Using the PnP PropertyFieldCodeEditor Control in an SPFx Web Part
language: PropertyFieldCodeEditorLanguages.HTML,
language: PropertyFieldCodeEditorLanguages.HTML,
language: PropertyFieldCodeEditorLanguages.HTML,
What We Will Build
The Web Part will contain two configurable properties:
description: a standard SPFx text field.htmlCode: an HTML source-code field usingPropertyFieldCodeEditor.
The HTML entered in the Property Pane will be passed from the Web Part class to the React component.
Install the PnP Property Controls Package
From the root folder of the existing SPFx solution, run:
npm install @pnp/spfx-property-controls --save
Restore the complete project dependencies:
npm install
Import the Control
Open the main Web Part file and add the following import:
import { PropertyFieldCodeEditor, PropertyFieldCodeEditorLanguages} from '@pnp/spfx-property-controls/lib/PropertyFieldCodeEditor';
PropertyFieldCodeEditor creates the Property Pane field.
PropertyFieldCodeEditorLanguages defines the language used by the editor.
Define the Web Part Properties
The Web Part requires a string property to store the HTML code:
export interface IPropertyFieldCodeEditorWpWebPartProps { description: string; htmlCode: string;}
The htmlCode property stores the value entered in the code editor.
SPFx persists this value together with the Web Part configuration.
Complete Web Part Code
The following is the complete PropertyFieldCodeEditorWpWebPart.ts implementation:
import * as React from 'react';import * as ReactDom from 'react-dom';import { Version } from '@microsoft/sp-core-library';import { type IPropertyPaneConfiguration, PropertyPaneTextField} from '@microsoft/sp-property-pane';import { BaseClientSideWebPart} from '@microsoft/sp-webpart-base';import { IReadonlyTheme} from '@microsoft/sp-component-base';import * as strings from 'PropertyFieldCodeEditorWpWebPartStrings';import PropertyFieldCodeEditorWp from './components/PropertyFieldCodeEditorWp';import { IPropertyFieldCodeEditorWpProps} from './components/IPropertyFieldCodeEditorWpProps';import { PropertyFieldCodeEditor, PropertyFieldCodeEditorLanguages} from '@pnp/spfx-property-controls/lib/PropertyFieldCodeEditor';export interface IPropertyFieldCodeEditorWpWebPartProps { description: string; htmlCode: string;}export default class PropertyFieldCodeEditorWpWebPart extends BaseClientSideWebPart<IPropertyFieldCodeEditorWpWebPartProps> { private _isDarkTheme: boolean = false; private _environmentMessage: string = ''; public render(): void { const element: React.ReactElement<IPropertyFieldCodeEditorWpProps> = React.createElement( PropertyFieldCodeEditorWp, { description: this.properties.description, htmlCode: this.properties.htmlCode } ); ReactDom.render(element, this.domElement); } protected onInit(): Promise<void> { return this._getEnvironmentMessage().then(message => { this._environmentMessage = message; }); } private _getEnvironmentMessage(): Promise<string> { if (this.context.sdks.microsoftTeams) { return this.context.sdks.microsoftTeams.teamsJs.app .getContext() .then(context => { let environmentMessage: string = ''; switch (context.app.host.name) { case 'Office': environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment; break; case 'Outlook': environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment; break; case 'Teams': case 'TeamsModern': environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment; break; default: environmentMessage = strings.UnknownEnvironment; } return environmentMessage; }); } return Promise.resolve( this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment ); } protected onThemeChanged( currentTheme: IReadonlyTheme | undefined ): void { if (!currentTheme) { return; } this._isDarkTheme = !!currentTheme.isInverted; const { semanticColors } = currentTheme; if (semanticColors) { this.domElement.style.setProperty( '--bodyText', semanticColors.bodyText || null ); this.domElement.style.setProperty( '--link', semanticColors.link || null ); this.domElement.style.setProperty( '--linkHovered', semanticColors.linkHovered || null ); } } protected onDispose(): void { ReactDom.unmountComponentAtNode(this.domElement); } protected get dataVersion(): Version { return Version.parse('1.0'); } protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { return { pages: [ { header: { description: strings.PropertyPaneDescription }, groups: [ { groupName: strings.BasicGroupName, groupFields: [ PropertyPaneTextField('description', { label: strings.DescriptionFieldLabel }), PropertyFieldCodeEditor('htmlCode', { label: 'Edit HTML Code', panelTitle: 'Edit HTML Code', initialValue: this.properties.htmlCode, onPropertyChange: this.onPropertyPaneFieldChanged, properties: this.properties, disabled: false, key: 'codeEditorFieldId', language: PropertyFieldCodeEditorLanguages.HTML, options: { wrap: true, fontSize: 20 } }) ] } ] } ] }; }}
Property Pane Group
The custom control is added to the groupFields collection of the Property Pane group:
groups: [ { groupName: strings.BasicGroupName, groupFields: [ PropertyPaneTextField('description', { label: strings.DescriptionFieldLabel }), PropertyFieldCodeEditor('htmlCode', { label: 'Edit HTML Code', panelTitle: 'Edit HTML Code', initialValue: this.properties.htmlCode, onPropertyChange: this.onPropertyPaneFieldChanged, properties: this.properties, disabled: false, key: 'codeEditorFieldId', language: PropertyFieldCodeEditorLanguages.HTML, options: { wrap: true, fontSize: 20 } }) ] }]
This group combines a native SPFx Property Pane control with a PnP custom Property Pane control.
Understanding the Control Configuration
Property name
PropertyFieldCodeEditor('htmlCode', {
The first argument identifies the Web Part property that will receive the editor value.
It corresponds to:
htmlCode: string;
After the user changes the content, the value becomes available through:
this.properties.htmlCode
Label
label: 'Edit HTML Code',
The label appears in the main Property Pane.
The user selects this field to open the complete editor experience.
Panel title
panelTitle: 'Edit HTML Code',
The panelTitle is displayed at the top of the editor panel.
Initial value
initialValue: this.properties.htmlCode,
The existing Web Part property value is loaded into the editor.
When the Web Part is edited again, the previously saved HTML is displayed.
Property change handler
onPropertyChange: this.onPropertyPaneFieldChanged,
This connects the PnP control to the standard SPFx property-change lifecycle.
When the code changes, SPFx updates the corresponding Web Part property.
Web Part properties
properties: this.properties,
The complete Web Part properties object is passed to the control.
The PnP control uses this object together with onPropertyChange to update htmlCode.
Disabled state
disabled: false,
When set to false, the editor can be opened and modified.
To prevent editing, use:
disabled: true,
Unique key
key: 'codeEditorFieldId',
The key uniquely identifies this Property Pane control.
Each instance should use a stable and unique key.
Editor language
language: PropertyFieldCodeEditorLanguages.HTML,
This example configures the editor for HTML.
The selected language affects syntax highlighting and the editing experience.
Other supported values include:
PropertyFieldCodeEditorLanguages.cssPropertyFieldCodeEditorLanguages.JavaScriptPropertyFieldCodeEditorLanguages.JSONPropertyFieldCodeEditorLanguages.HandlebarsPropertyFieldCodeEditorLanguages.HTMLPropertyFieldCodeEditorLanguages.PlainTextPropertyFieldCodeEditorLanguages.SassPropertyFieldCodeEditorLanguages.TypeScriptPropertyFieldCodeEditorLanguages.XML
The language should match the type of content stored by the Web Part.
Editor Options
The options object configures the underlying code editor.
options: { wrap: true, fontSize: 20}
Line wrapping
wrap: true
Long lines are wrapped inside the editor instead of requiring horizontal scrolling.
Font size
fontSize: 20
This sets the editor font size to 20 pixels.
A smaller value can be used when more code needs to be visible at the same time:
options: { wrap: true, fontSize: 14}
React Component Properties
Create or update the component properties interface:
export interface IPropertyFieldCodeEditorWpProps { description: string; htmlCode: string;}
This interface receives the values passed by the Web Part class.
React Component Example
The React component can display the description and the HTML source stored in the property.
import * as React from 'react';import { IPropertyFieldCodeEditorWpProps} from './IPropertyFieldCodeEditorWpProps';const PropertyFieldCodeEditorWp: React.FC<IPropertyFieldCodeEditorWpProps> = ({ description, htmlCode }) => { return ( <section> <h2>PropertyFieldCodeEditor</h2> <p>{description}</p> <h3>HTML source</h3> <pre> <code> {htmlCode} </code> </pre> </section> );};export default PropertyFieldCodeEditorWp;
This version displays the HTML as source code. React escapes the content, so HTML tags are shown as text instead of being rendered by the browser.
Rendering the HTML
When the Web Part must render the configured HTML, React provides dangerouslySetInnerHTML:
import * as React from 'react';import { IPropertyFieldCodeEditorWpProps} from './IPropertyFieldCodeEditorWpProps';const PropertyFieldCodeEditorWp: React.FC<IPropertyFieldCodeEditorWpProps> = ({ description, htmlCode }) => { return ( <section> <h2>{description}</h2> <div dangerouslySetInnerHTML={{ __html: htmlCode || '' }} /> </section> );};export default PropertyFieldCodeEditorWp;
This approach should only be used when the HTML comes from a trusted source.
Allowing arbitrary HTML to be rendered can introduce cross-site scripting risks. In a production solution, the content should be validated or sanitized before being inserted into the page.
How the Property Binding Works
The complete data flow is:
PropertyFieldCodeEditor ↓this.properties.htmlCode ↓render() ↓React.createElement() ↓IPropertyFieldCodeEditorWpProps ↓PropertyFieldCodeEditorWp component
The Property Pane updates:
this.properties.htmlCode
The Web Part passes the property to React:
htmlCode: this.properties.htmlCode
The component receives it through its props:
const PropertyFieldCodeEditorWp: React.FC<IPropertyFieldCodeEditorWpProps> = ({ htmlCode }) => {
This is the same SPFx property-binding pattern used by native Property Pane controls.
Optional Default Value
A default HTML value can be initialized during onInit:
protected async onInit(): Promise<void> { if (!this.properties.htmlCode) { this.properties.htmlCode = '<h2>Hello from PropertyFieldCodeEditor</h2>'; } this._environmentMessage = await this._getEnvironmentMessage();}
The editor will then open with the initial HTML:
<h2>Hello from PropertyFieldCodeEditor</h2>
Testing the Web Part
Start the SPFx development server:
heft start
Add the Web Part to the SharePoint Workbench or a modern SharePoint page.
Then:
- Edit the Web Part.
- Open the Property Pane.
- Locate Edit HTML Code.
- Open the code editor.
- Enter HTML code.
- Apply the change.
- Confirm that the React component receives the new value.
Example HTML:
<section> <h2>PnP Property Controls</h2> <p>This HTML was configured from the Web Part Property Pane.</p></section>
About the Generated Template Fields
The Web Part still contains the following fields from the standard SPFx template:
private _isDarkTheme: boolean = false;private _environmentMessage: string = '';
The values are populated by onThemeChanged and _getEnvironmentMessage, but they are not currently passed to the React component.
This does not prevent the Web Part from working.
They can be retained for future use or removed if the Web Part does not require theme or environment information.
When to Use PropertyFieldCodeEditor
This control is useful when a Web Part needs configurable source content such as:
- HTML templates.
- CSS rules.
- JSON configuration.
- JavaScript snippets.
- Handlebars templates.
- XML structures.
- TypeScript examples.
- Custom formatting definitions.
It provides a much better editing experience than a standard multiline PropertyPaneTextField.
Conclusion
The PropertyFieldCodeEditor brings a complete source-code editing experience to the SPFx Property Pane.
In this example, it stores HTML in the htmlCode Web Part property, connects to the standard SPFx property-change lifecycle, and passes the configured value to a React component.
The most important parts of the implementation are:
PropertyFieldCodeEditor('htmlCode', {
initialValue: this.properties.htmlCode,
onPropertyChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
language: PropertyFieldCodeEditorLanguages.HTML,
Together, these settings connect the PnP code editor to the SPFx Web Part properties.
