Interaction 1 (Baseline v1.0.0): Build a Generic Copilot Prompt Hub SPFx Web Part for SharePoint

Why this exists

Most organizations try Copilot in SharePoint and quickly hit the same issue:

  • People ask inconsistent questions.
  • Outputs vary wildly depending on how the prompt is phrased.
  • Adoption stalls because users don’t have a repeatable way to “prompt well.”

Before you build agents, connect to business lists, or automate actions, the fastest win is a prompt standardization layer embedded directly in SharePoint pages.

Interaction 1 is that layer: a Generic Copilot Prompt Hub web part that works on any modern page and provides curated prompt templates users can copy/paste into Copilot.

This approach uses the SharePoint Framework (SPFx), Microsoft’s recommended extensibility model for modern SharePoint experiences.

Primary references (Microsoft Learn):


What you will build (Scope of Interaction 1)

A Prompt Hub web part with:

  1. A left panel containing prompt templates (click to select).
  2. A right panel showing a prompt preview (read-only).
  3. Buttons:
    • Copy prompt
    • Copy with context (adds a short generic context header)
  4. Templates stored in the Property Pane as JSON (templatesJson) so prompt content can be updated without changing code.

Important: this baseline is not an agent and does not integrate with lists, Graph, or external APIs. It is purely a UI accelerator for Copilot usage.


Prerequisites

  • A working SPFx developer environment (Node + Yeoman + Gulp)
  • A SharePoint Online tenant/site where you can test
  • Optional: access to the tenant app catalog to deploy the .sppkg

Microsoft Learn guidance:


Architecture (simple by design)

Interaction 1 is intentionally minimal:

  • Property Pane JSON → templates are configured by the page editor/admin.
  • React UI → renders the template list + prompt preview.
  • Clipboard API → copies selected prompt to the user’s clipboard.

This design avoids early friction:

  • No REST calls
  • No list schema mapping
  • No permissions troubleshooting
  • Fast deployment and immediate value

Step-by-step implementation

Step 1 — Scaffold the SPFx solution

Create a new project folder and run:

yo @microsoft/sharepoint

Recommended selections:

  • Framework: React
  • Web part name: CopilotPromptHub

Reference:


Step 2 — Run locally (Workbench)

Start the local dev server:

gulp serve

Add the web part to the workbench and confirm:

  • It renders
  • The property pane opens

SPFx toolchain reference:


Step 3 — Implement baseline Prompt Hub behavior

Baseline rules:

  • Templates must be read from templatesJson (Property Pane)
  • JSON parsing must be safe (show a friendly error on invalid JSON)
  • Each template must contain: id, title, prompt
  • Copy buttons must work consistently

Property pane integration reference:


Baseline v1.0.0 source code (copy/paste ready)

1) ICopilotPromptHubProps.ts

export interface IPromptTemplate {
id: string;
title: string;
prompt: string;
}
export interface ICopilotPromptHubProps {
description: string;
templatesJson: string; // JSON array of IPromptTemplate
}

2) CopilotPromptHubWebPart.ts

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import CopilotPromptHub from './components/CopilotPromptHub';
import { ICopilotPromptHubProps } from './components/ICopilotPromptHubProps';
export interface ICopilotPromptHubWebPartProps {
description: string;
templatesJson: string;
}
export default class CopilotPromptHubWebPart extends BaseClientSideWebPart<ICopilotPromptHubWebPartProps> {
public render(): void {
const element: React.ReactElement<ICopilotPromptHubProps> = React.createElement(
CopilotPromptHub,
{
description: this.properties.description,
templatesJson: this.properties.templatesJson
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
private getDefaultTemplates(): string {
const defaults = [
{
id: "status-summary",
title: "Write a status summary",
prompt:
"Summarize the current page in 6 bullet points.\n" +
"Then propose the next 3 actions with owners and due dates (use placeholders if unknown)."
},
{
id: "extract-decisions",
title: "Extract decisions & action items",
prompt:
"From this page, extract:\n" +
"1) Decisions made\n" +
"2) Action items (who/what/when)\n" +
"3) Open questions\n" +
"Return as a structured list."
},
{
id: "risk-review",
title: "Risk review",
prompt:
"Review this content and identify:\n" +
"- Risks\n" +
"- Dependencies\n" +
"- Missing information\n" +
"Provide a short mitigation plan."
}
];
return JSON.stringify(defaults, null, 2);
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: { description: "Copilot Prompt Hub settings" },
groups: [
{
groupName: "General",
groupFields: [
PropertyPaneTextField('description', { label: 'Description' })
]
},
{
groupName: "Prompt Templates",
groupFields: [
PropertyPaneTextField('templatesJson', {
label: 'Templates JSON',
multiline: true,
rows: 18,
value: this.properties.templatesJson || this.getDefaultTemplates()
})
]
}
]
}
]
};
}
}

3) CopilotPromptHub.tsx

import * as React from 'react';
import styles from './CopilotPromptHub.module.scss';
import { ICopilotPromptHubProps, IPromptTemplate } from './ICopilotPromptHubProps';
export default function CopilotPromptHub(props: ICopilotPromptHubProps) {
const [templates, setTemplates] = React.useState<IPromptTemplate[]>([]);
const [error, setError] = React.useState<string | null>(null);
const [selected, setSelected] = React.useState<IPromptTemplate | null>(null);
React.useEffect(() => {
parseTemplates(props.templatesJson);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.templatesJson]);
function parseTemplates(jsonText: string): void {
try {
setError(null);
if (!jsonText || !jsonText.trim()) {
setTemplates([]);
setSelected(null);
return;
}
const parsed = JSON.parse(jsonText) as IPromptTemplate[];
if (!Array.isArray(parsed)) {
throw new Error("Templates JSON must be an array.");
}
parsed.forEach(t => {
if (!t.id || !t.title || !t.prompt) {
throw new Error("Each template must have id, title, and prompt.");
}
});
setTemplates(parsed);
setSelected(parsed[0] || null);
} catch (e: any) {
setError(e?.message || "Invalid templates JSON");
setTemplates([]);
setSelected(null);
}
}
async function copyToClipboard(text: string): Promise<void> {
await navigator.clipboard.writeText(text);
alert("Prompt copied to clipboard.");
}
return (
<div className={styles.root}>
<div className={styles.header}>
<div className={styles.title}>Copilot Prompt Hub</div>
<div className={styles.subtitle}>
{props.description || "Reusable prompts to speed up Copilot work."}
</div>
</div>
{error && (
<div className={styles.errorBox}>
<b>Templates error:</b> {error}
</div>
)}
<div className={styles.grid}>
<div className={styles.left}>
<div className={styles.sectionTitle}>Templates</div>
<div className={styles.list}>
{templates.map(t => (
<button
key={t.id}
className={`${styles.listItem} ${selected?.id === t.id ? styles.active : ''}`}
onClick={() => setSelected(t)}
>
{t.title}
</button>
))}
{templates.length === 0 && !error && (
<div className={styles.empty}>No templates configured.</div>
)}
</div>
</div>
<div className={styles.right}>
<div className={styles.sectionTitle}>Prompt</div>
<textarea
className={styles.promptBox}
readOnly
value={selected?.prompt || ""}
placeholder="Select a template..."
/>
<div className={styles.actions}>
<button
className={styles.primaryBtn}
disabled={!selected}
onClick={() => void copyToClipboard(selected!.prompt)}
>
Copy prompt
</button>
<button
className={styles.secondaryBtn}
disabled={!selected}
onClick={() => void copyToClipboard(`Context: I am working in SharePoint.\n\n${selected!.prompt}`)}
>
Copy with context
</button>
</div>
<div className={styles.tip}>
Tip: paste the prompt into Copilot. Better page structure (headings + bullets) improves results.
</div>
</div>
</div>
</div>
);
}

4) CopilotPromptHub.module.scss

.root {
font-family: "Segoe UI", Arial, sans-serif;
border: 1px solid #e6e6e6;
border-radius: 12px;
padding: 14px;
background: #fff;
}
.header { margin-bottom: 12px; }
.title { font-size: 18px; font-weight: 650; }
.subtitle { font-size: 12px; color: #666; margin-top: 4px; }
.errorBox {
background: #fff4f4;
border: 1px solid #ffd7d7;
color: #5a0000;
padding: 10px;
border-radius: 10px;
margin-bottom: 12px;
}
.grid {
display: grid;
grid-template-columns: 260px 1fr;
gap: 12px;
}
.sectionTitle { font-weight: 600; margin-bottom: 8px; }
.left {
border: 1px solid #f0f0f0;
border-radius: 12px;
padding: 12px;
background: #fafafa;
}
.list { display: flex; flex-direction: column; gap: 8px; }
.listItem {
text-align: left;
border: 1px solid #ddd;
background: #fff;
padding: 10px;
border-radius: 10px;
cursor: pointer;
}
.active {
border-color: #2b6cb0;
box-shadow: 0 0 0 2px rgba(43,108,176,0.15);
}
.empty { color: #777; font-size: 12px; padding: 6px 0; }
.right {
border: 1px solid #f0f0f0;
border-radius: 12px;
padding: 12px;
background: #fff;
}
.promptBox {
width: 100%;
height: 220px;
border-radius: 10px;
border: 1px solid #ddd;
padding: 10px;
font-size: 12px;
font-family: Consolas, "Courier New", monospace;
}
.actions { margin-top: 10px; display: flex; gap: 10px; }
.primaryBtn {
padding: 9px 12px;
border-radius: 10px;
border: 1px solid #2b6cb0;
background: #2b6cb0;
color: #fff;
font-weight: 600;
cursor: pointer;
}
.secondaryBtn {
padding: 9px 12px;
border-radius: 10px;
border: 1px solid #777;
background: #fff;
color: #222;
font-weight: 600;
cursor: pointer;
}
.tip { margin-top: 10px; font-size: 12px; color: #555; }

Packaging & deployment (complete the baseline)

Once your web part works locally, package it for production deployment:

gulp bundle --ship
gulp package-solution --ship

Then:

  1. Upload the .sppkg to your tenant app catalog
  2. Deploy the solution
  3. Add the app to the target site
  4. Add the web part to a modern page

Reference:


Definition of Done (Interaction 1)

Your baseline is complete when:

  • Web part renders on a modern page
  • Property pane accepts valid JSON templates
  • Invalid JSON shows a friendly error message (page does not break)
  • Copy buttons copy exactly what the user sees
  • Solution is packaged and deployed via the app catalog and usable on pages

What comes next (Interaction 2 preview)

Interaction 2 keeps it list-free but makes it “enterprise-ready”:

  • Categories + search box
  • Favorites (local storage)
  • Import/export prompt sets (JSON)
  • Preconfigured entries (simplify adding standard prompt packs)

Reference:


Final summary tables

Step-by-step summary

StepActionResult
1Scaffold SPFx React web partWorking SPFx project (MS Learn: Hello World)
2Run locally (gulp serve)Validate UI + property pane
3Implement Prompt Hub UI + JSON parsingPrompt templates selectable & previewable
4Package (--ship).sppkg ready
5Deploy to app catalogWeb part usable on SharePoint pages

Technical mapping

ComponentTechnologyWhy it’s used
Web part platformSPFxMicrosoft’s modern SharePoint extensibility model
UI frameworkReactCommon SPFx pattern supported by MS Learn tutorials
ConfigurationProperty PaneAllows prompt updates without rebuilding
Copy behaviorClipboard APIFast workflow: select → copy → paste into Copilot
DeploymentApp Catalog + .sppkgStandard SPFx enterprise deployment path

Edvaldo Guimrães Filho Avatar

Published by