Your First Web Part: Real Code, Dissected

Lesson 4: Your First Web Part โ€” Real Code, Dissected

Scaffolding gives you a working web part in seconds; understanding it takes one careful read. We'll walk the two files that matter most โ€” the manifest (how SharePoint knows your component) and the web part class (what it renders) โ€” from a no-framework ("vanilla" TypeScript) scaffold.

The manifest: your component's ID card

Every SPFx component has a .manifest.json. The GUID in it is sacred: it's how the app catalog, the site, and the runtime all refer to this specific web part. Change it and you've created a different component. The scaffolded manifest looks like this:

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
  "id": "a12b3456-78c9-4def-a012-3456789abcde",
  "alias": "AnnouncementsWebPart",
  "componentType": "WebPart",
  "version": "*",
  "manifestVersion": 2,
  "supportsThemeVariants": true,
  "supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
  "preconfiguredEntries": [{
    "groupId": "5c31a052-22c4-4f9a-b1d1-1c3f2c4b8e11",
    "group": { "default": "Other" },
    "title": { "default": "Announcements" },
    "description": { "default": "Shows recent announcements from a list" },
    "officeFabricIconFontName": "Megaphone",
    "properties": { "title": "Announcements" }
  }]
}
  • id โ€” unique component identity (generate once, never reuse across components).
  • supportedHosts โ€” where authors can add it (SharePoint pages, Teams, full-page).
  • preconfiguredEntries โ€” the picker entry: toolbox group, title, icon, and the default properties a new instance starts with.

The web part class

A no-framework web part is one TypeScript class extending BaseClientSideWebPart, typed with your properties interface. The scaffolded version (trimmed to the essentials):

import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import {
  IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { escape } from '@microsoft/sp-lodash-subset';

export interface IAnnouncementsWebPartProps {
  title: string;          // configurable via the property pane
}

export default class AnnouncementsWebPart
  extends BaseClientSideWebPart<IAnnouncementsWebPartProps> {

  public render(): void {
    // this.domElement is the empty div SharePoint gave this web part
    this.domElement.innerHTML = `
      <div class="awp">
        <h2>${escape(this.properties.title)}</h2>
        <p>Hello from my first web part!</p>
      </div>`;
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0');
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [{
        header: { description: 'Announcements settings' },
        groups: [{
          groupName: 'Display',
          groupFields: [
            PropertyPaneTextField('title', { label: 'Heading' })
          ]
        }]
      }]
    };
  }
}

Reading the important lines

  • render() is the contract: whenever the web part must (re)draw, SPFx calls it โ€” on load, after a property-pane change, on canvas resize. It's your job to write the DOM (or hand off to a framework like React).
  • this.domElement โ€” the container the page gave you. Reassigning innerHTML on each render is fine at this scale; more complex web parts build a tree and diff it.
  • escape() โ€” always escape user-supplied strings before injecting HTML. Property values are content, not code โ€” treat them as hostile.
  • Property pane โ€” getPropertyPaneConfiguration() describes the edit panel on the right. PropertyPaneTextField('title', ...) binds the Title field to the title property; type a new value and render() runs again automatically.
Real project structure: as a web part grows, split it โ€” one file for the web part shell (properties + pane + wiring), separate modules for rendering/data. The React scaffold does this for you with a components/ folder. The "no framework" option keeps you close to the metal while you learn; nothing stops you adding React to the same project later.
Common rookie bug: forgetting that render() re-fires. If you attach event listeners or start background work in render() unconditionally, you'll stack duplicates on every property change. Guard with a flag, or better, use lifecycle hooks like onInit() for one-time setup.

๐Ÿง  Knowledge Check

1. What happens if you change the id GUID in a web part's manifest after publishing?

2. When does SharePoint call your web part's render() method?

3. Why should property values be passed through escape() (or rendered as text) before going into innerHTML?

Further Reading