In skybow solutions built on SharePoint Framework (SPFx), you can call Microsoft Graph directly from a Client-Side Action and reuse the returned values in subsequent actions.
This guide shows how to:
- Prepare a Graph Search API request against a Copilot connector (example: CSV; works for other connector types too).
- Execute the call with
spfxContext.msGraphClientFactory. - Capture the response and return the
itemslist. - Add a Rich Text Control to your form, inject custom styles and a target
<div>. - Create a second Execute Script action that sorts results (optional), builds a table, and renders the HTML into the Rich Text control using jQuery.
- Final result will look like this:
Table of contents
- Step 1 — Implement the Graph API Call in an Execute Script Action (Load Items)
- Step 2 — Add a Rich Text Control and Insert Styles + Target Div
- Step 3 — Create a Second Execute Script Action (Render Table)
Prerequisites
- A skybow form/page with access to SPFx context (
spfxContext). - Microsoft Graph permissions configured for your SPFx package (e.g., Search API).
Ensure consent has been granted by a tenant admin in the SharePoint Admin Center -> Advanced -> API Access. - A configured external connection (e.g., Copilot connector of type CSV) with its connectionId (e.g.,
CSV1). - jQuery available on the page (skybow Forms typically include it).
Step 1 - Implement the Graph API Call in an Execute Script Action (Load Items)
Create an Execute Script action (e.g., name it Execute_script_LoadItems) that queries your external connection via the Graph Search API and returns a list of items.
Note: The code below queries an external connection and requests fields commonly present in the CSV example. You can adapt the
viewFieldsto match your connector’s schema.
// Parameters
var connectionId = "CSV1";
var searchText = ""; // Example: "L" to filter
var searchField = "ModelName";
var viewFields = ["Id", "ModelName", "Type", "Horsepower", "ElectricRangeKM", "EstimatedPriceUSD"];
// Build dynamic search query
var searchQuery = {
queryString: "*",
queryTemplate: `${searchField}: *`
};
if (searchText) {
searchQuery = {
queryString: searchText,
queryTemplate: `${searchField}: * AND ${searchField}:{searchTerms}*`
};
}
// Execute Graph Search API via msGraphClientFactory and return items
return new Promise((resolve, reject) => {
spfxContext.msGraphClientFactory.getClient().then((client) => {
var body = {
requests: [
{
entityTypes: ["externalItem"],
contentSources: [`/external/connections/${connectionId}`],
query: searchQuery,
from: 0,
size: 100,
fields: viewFields
}
]
};
client.api('/search/query').post(body).then(response => {
var items = [];
var hits = response?.value?.[0]?.hitsContainers?.[0]?.hits;
if (hits) {
// Extract the properties from the external items
items = hits.map(hit => hit.resource.properties);
}
resolve(items); // Return to be consumed by subsequent actions
}).catch((ex) => {
reject(ex);
});
});
});
This action returns an array of objects (items) that will be referenced by the second action.
Step 2 - Add a Rich Text Control and Insert Styles + Target Div
- Open your skybow Form Designer.
- Drag a Rich Text Control onto your form where the table should appear.
- Click the Edit Source button in the control’s command bar.
- Paste the HTML/CSS snippet below:
<div>
<style>
#externalDataTable table {
width: 60%;
border-collapse: collapse;
margin: 20px 0;
font-size: 16px;
text-align: left;
}
#externalDataTable th,
#externalDataTable td {
padding: 12px 15px;
border: 1px solid #ddd;
}
#externalDataTable th {
background-color: #b7d4d4;
color: black;
}
#externalDataTable tr:nth-child(even) {
background-color: #f2f2f2;
}
#externalDataTable tr:hover {
background-color: #ddd;
}
</style>
</div>
<div id="externalDataTable"></div>
This adds a target container with the ID externalDataTable and table styles that will apply to the HTML generated in the next step.
Step 3 - Create a Second Execute Script Action (Render Table)
Create another Execute Script action (e.g., name it Execute_script_RenderTable) that reads the output from the first action, optionally sorts the results, then builds and injects the table HTML into #externalDataTable using jQuery.
In skybow, you can reference the return value from the previous action using:
[[@Actions.<ActionName>.ReturnValue]]
// Read items from the first Execute Script action
const items = [[@Actions.Execute_script_LoadItems.ReturnValue]];
// (Optional) Sort by Model Name; case-insensitive and null-safe
items.sort((a, b) => {
const am = (a.modelName || a.ModelName || '').toString().toLowerCase();
const bm = (b.modelName || b.ModelName || '').toString().toLowerCase();
return am.localeCompare(bm, undefined, { sensitivity: 'base' });
});
// Build table HTML
const tableHTML = `
<table>
<thead>
<tr>
<th>Model Name</th>
<th>Type</th>
<th>Horsepower</th>
<th>Electric Range (KM)</th>
<th>Estimated Price (USD)</th>
</tr>
</thead>
<tbody>
${
items.map(item => {
// Support both camelCase and PascalCase
const modelName = item.modelName ?? item.ModelName ?? '';
const type = item.type ?? item.Type ?? '';
const horsepower = item.horsepower ?? item.Horsepower ?? '';
const electricRangeKM = item.electricRangeKM ?? item.ElectricRangeKM ?? '';
const estimatedPriceUSD = item.estimatedPriceUSD ?? item.EstimatedPriceUSD ?? '';
// Basic escape function
const esc = s => String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
return `
<tr>
<td>${esc(modelName)}</td>
<td>${esc(type)}</td>
<td>${esc(horsepower)}</td>
<td>${esc(electricRangeKM)}</td>
<td>${esc(estimatedPriceUSD)}</td>
</tr>
`;
}).join('')
}
</tbody>
</table>
`;
// Inject into the Rich Text control target div
jQuery('#externalDataTable').empty().append(tableHTML);
// Optional return for logging/diagnostics
return 'Table rendered';
Tips & Troubleshooting
-
Connector Schema / Field Names
External connectors may return properties in PascalCase (ModelName) or camelCase (modelName). The robust script handles both; adjust if your schema differs. -
jQuery Availability
If$is not defined, usejQuery(...). Ensure the form loads jQuery (skybow Forms typically do). -
Error Handling
Addtry/catcharound sorting and rendering if your data may contain unexpected values. Consider logging the length ofitemsor capturing empty results. -
Performance
For large datasets, consider limitingsizein the request, adding server-side query filters, or implementing client-side pagination. -
Security
Use basic HTML escaping when rendering user-supplied text to avoid injecting unsafe markup.
What You Get
- A reusable pattern to call Graph from skybow client-side actions.
- A styled, dynamic table inside a Rich Text control.
- A setup that works for CSV and other connector types, as well as other Graph endpoints beyond
/search/query.