I posted this Dynamics 365 CRM Field Service Report I had built on the r/PowerApps subreddit it blew up and got 15k views. I received a lot of postivie feedback.

Sample Field Service Report
Here’s a glimpse of what people had to say:
u/MadeInWestGermany
I think it‘s exactly what most clients wish for… Great work👍u/mrf1uff1es
I thought I was looking at Crystal reports in this subreddit. Nice job.
And many others asked how they could build the same:
u/malhosainy
That’s a good looking report, I appreciate more details on how is that done, or if there is a tutorial to create a similar report. Thanks!u/Franki3B_
Great job. Could you share some of your steps of how you got from the start to this?u/National_Ad_3995_
Love the report layout and the dual signature. This report covers a lot of bases clients request. Any chance you would be able to share a sample of your multiple columns repeating layout?
Let’s build it together
🏁 Before You Start
Who this guide is for:
This guide is for Dynamics 365 consultants and developers with some React/TypeScript familiarity who want to customize Field Service reports quickly without rebuilding from scratch.
In this guide, I’ll show you how to take Microsoft’s sample Field Service report solution and customize it to your own data and layout needs inside Dynamics 365 CRM without rebuilding everything from scratch. You’ll learn how to set up the base solution quickly and understand where the main pieces of logic are so you can adapt them for your own scenarios.
⚠️ What This Guide Covers (and Doesn’t)
This isn’t a deep dive into React, TypeScript, or CSS styling. The goal here isn’t to teach frontend development, but to highlight which parts of the report’s code are safe to modify and how to hook in your own data from Dataverse using Xrm.WebApi.
Introduction & Installation
First of all you need to start with the most important steps from the official microsoft tutorial which I’m also including here:
-
Download the official ReportingSolution_managed.zip
-
Import the reporting solution into your environment. The import installs a reporting form, a command for the command bar, and includes a sample report. We recommend importing the solution as a managed solution.
-
Find the Field Service Mobile app module in your list of Dynamics 365 apps and select the ellipsis (…) > Open in App Designer.
-
In the navigation, select the Bookings form.
-
On the right side pane, select the ellipsis (…) for the Reporting form and select Add. This step enables the Reporting form for the Bookable Resource Booking entity.
-
Select Save & Publish.
-
Test the sample reports
Download the source code
Now that you’ve imported and tested the solution, we can begin by downloading the official source code of the solution, you can do this manually or, if you prefer, use Git to clone the repo.
Whichever method you decide to use just make sure to navigate to "Field Service/Component Library/FSMobile"
and extract the Service Report
folder.
🛠 Customization Scope
In this section, we’ll look at where the report’s logic lives and how to safely plug in your own data without touching the report’s styling or layout code.
Customizing the control
Prerequisites
- Code Editor (VSCode)
- Node.js (LTS version is recommended)
- Knowledge of React, Typescript and Xrm.WebApi
- Microsoft Power Platform CLI (Use the Visual Studio Code extension)
Initial Run & Test
Navigate into SERVICE REPORT/Controls/ReportPreview
and from the terminal run
npm install
after you’re done installing, run npm start
then navigate tohttp://localhost:8181/
and you should see the PowerApps component framework Test Environment along with your Contoso Service Report.
This is what you should see

Contoso Service Report
Understanding the file structure
📁 SERVICE REPORT
📁 Controls
📁 ReportPreview
📁 DataProviders # Queries to fetch data
📁 models # Define data models
📁 SampleReport # Service Report code
📄 ControlManifest.Input.xml # PCF configuration file
📄 index.ts # Entry point passing data to report
📄 ReportViewer.tsx
📁 solutions # Built solutions
📄 README.md
📄 .gitignore
📄 dirs.proj
With the above structure in mind we can start customizing the report, I’m not going to simply give you the exact code I have because it’s specific to my case and not yours, but I’ll show you what you can do to adapt the report to your use case.
TheControlManifest.Input.xml
file is the most important, it defines the data, resources and features that are passed down from the Dynamics FORM to the Report. If you want to learn more about it you can learn here
. In short:
- The
control
element defines the component’s namespace, version, and display information, it contains a very important tag calledversion
which you need to increment each time you build your code in a solution ready to be published. - The
<property>
element defines a specific, configurable piece of data that the component expects from the Microsoft Dataverse, so if you want another signature field you can simply define it here and then pass it down from the Reporting Form to the Report. - The
resources
element refers to the resource files that component requires to implement it’s visualization. - The
feature-usage
element acts as a wrapper around the uses-feature elements. - The
uses-feature
indicates which feature the code components want to use such as Utility, WebApi and more.
TheDataProviders/GetReportData.ts
file contains the code used for fetching data from Dataverse, if you want to add queries or modify existing ones you need to have knowledge of Xrm.WebApi
or you can use a tool like Dataverse REST Builder
to simplify writing queries for you.
Before making changes to a query or adding a new one you need to modify or add the appropriate model which represents the structure of a record from Dataverse in ReportPreview/models
, so if I were to add a query to fetch user data then I would start by adding a new model class called User.
Once that is done, in the case of a new query, you need to call your new function when the report is initially loaded and that is done inside the getDataFetchPromises()
function in index.ts
which passes the data to the SampleReport.tsx
component.
If you have knowledge of React, Typescript and Xrm.WebApi you can proceed on your own and skip to Package & publish., if not I’m going to show you a very basic example of adding a new model, writing the query which returns data based on that model and then rendering that data in the report.
You can experiment with the sample queries and learn a lot, so I’m going to show you something different, what if I need to display the current user’s data, how would I go about doing it ?
Displaying current user’s data
- Define the data we need
- Retrieve the current user’s ID
- Fetch User Data
- Display it
Define the data model
Write down the logical names of the fields you want to display then go into theReportPreview/models/ReportViewerModel.ts
file and define the User & BusinessUnit classes.
export class User {
fullname: string;
email: string;
businessUnit: BusinessUnit;
}
export class BusinessUnit {
divisionName: string;
phoneNumber: string;
}
Retrieve the current user’s ID
Navigate to theReportPreview/DataProviders/GetReportData.ts
file, where you’ll find the GetReportData
class, using one of the attributes of this class we can gain direct access
to the user’s ID.
export class GetReportData {
private bookingID: string;
private workOrderID: string;
constructor(private context: ComponentFramework.Context<IInputs>) {
this.bookingID = context.parameters?.BookingId?.formatted;
const workOrder = context.parameters?.WorkOrder?.raw;
this.workOrderID = workOrder ? workOrder[0]?.id : undefined;
}
/*
The "private context" inside the constructor is a TypeScript shortcut which:
1. Declares a private property called context on your class
2. Assigns the passed argument to it.
So effectively, it’s the same as if you had written:
private context: ComponentFramework.Context<IInputs>;
constructor(context: ComponentFramework.Context<IInputs>) {
this.context = context;
}
*/
// Inside the context we have a userSettings field and we can use it to retrieve the current user's ID.
// Keep this in mind since we're not going to write this in a separate function but we're going to use it in STEP 3
const currentUserId = this.context.userSettings.userId;
}
...
Fetch User Data
Let’s write our new query function inReportPreview/DataProviders/GetReportData.ts
public getCurrentUserData = async (): Promise<User> => {
// retrieve the current user's ID from the context.userSettings object as seen in Step 2
const currentUserId = this.context.userSettings.userId;
// execute the query and fetch
const userData = await this.context.webAPI.retrieveRecord(
"systemuser",
currentUserId,
"?$select=fullname,internalemailaddress,_businessunitid_value&$expand=businessunitid($select=divisionname,address1_telephone1)"
);
// Define empty User object
let user: User = {
fullname: "",
email: "",
businessUnit: {
divisionName: "",
phoneNumber: "",
},
};
// assign data to user object if successfully fetched
if (userData) {
user = {
fullname: userData.fullname,
email: userData.internalemailaddress,
businessUnit: {
divisionName: userData.businessunitid?.divisionname ?? "",
phoneNumber: userData.businessunitid?.address1_telephone1 ?? "",
},
};
}
return user;
};
Display User’s data
Navigate inside theReportPreview/models/ReportViewerModel.ts
file and add a User field to the ReportViewerProps interface
export interface ReportViewerProps {
booking: Booking;
serviceInfo: ServiceInfo;
context: ComponentFramework.Context<IInputs>;
products: Array<Product>;
servicetasks: Array<ServiceTask>;
services: Array<Service>;
user: User; // <<-- new
signature: string;
isSpinnerVisible: boolean;
}
Then navigate inside theReportPreview/index.ts
file and add initialize theuser
prop as undefined.
this.props = {
user: undefined, // <<---
booking: undefined,
serviceInfo: undefined,
products: [],
servicetasks: [],
services: [],
signature: this.signature,
context: context,
isSpinnerVisible: false,
};
Find thegetDataFetchPromises()
function inside the same file and add call the getCurrentUserData() like so
const updateUserData = dataGetter.getCurrentUserData().then(
(user) => {
this.props.user = user;
this.renderReportViewer(this.props);
},
(err) => {
// eslint-disable-next-line no-console
console.log("Failed to retrieve current user's data: ", err);
}
);
...
return [
updateUserData, // <<--
updateBookingData,
updateProductData,
updateTasksData,
updateServiceInfo,
updateServicesData,
];
Navigate inside theReportPreview/SampleReport/SampleReport.tsx
and add a user as prop of the SampleReport component.
export default {
booking,
products,
servicetasks,
serviceInfo,
signature,
services,
user, // <<--
};
And in the end go inside theReportViewer.tsx
file and add the user as a prop for the SampleReport component.
<SampleReport
booking={this.props.booking}
products={this.props.products}
servicetasks={this.props.servicetasks}
serviceInfo={this.props.serviceInfo}
signature={this.props.signature}
user={this.props.user} // <<--
{...this.props}
/>
Now we can finally get started with displaying the data, considering that the current user whose data we just fetched is an approver we can add their data under the signature just like so:
<div style={{ marginTop: "40px" }}>
<SectionTitle>Approved By</SectionTitle>
<FieldInfo
name="Name"
value={user?.fullname}
valueStyles={styles.singleColValue}
/>
<FieldInfo
name="Email"
value={user?.email}
valueStyles={styles.singleColValue}
/>
<FieldInfo
name="Business Unit"
value={user?.businessUnit?.divisionName}
valueStyles={styles.singleColValue}
/>
<FieldInfo
name="Phone"
value={user?.businessUnit?.phoneNumber}
valueStyles={styles.singleColValue}
/>
</div>
Now we can proceed with building the PCF using thenpm start
comand and this would be the result.

Contoso Field Service Report v2
Package & Publish
You can follow this official tutorial from Microsoft
At this point, you’ve seen how to take Microsoft’s sample Field Service report, understand its structure, and extend it with your own data and logic. The beauty of this approach is that you don’t need to rebuild everything from scratch you can focus on the parts that matter most to your business case.
Enjoy!