Supporting a new SPFx version — maintainer-only
Process for adding a new spfxVersion target. Formats are harvested from official npm packages, not docs or memory — verify byte-level. See Microsoft docs: SPFx compatibility and Release 1.23.
Source of truth
Single file packages/core/src/versions.ts:13 (SPFX_VERSIONS, SPFX_DEFAULT_TARGET, SPFX_TARGETS, spfxNpmVersion()). All consumers derive from it: config default, CLI prompts/validation, rspfx doctor, scaffold pins. Adding a target is one entry. Current matrix → compatibility.md#spfx-version-matrix.
Detect a new release
| Signal | Where |
|---|---|
| npm dist-tags | npm dist-tag ls @microsoft/generator-sharepoint (@latest=GA, @next=preview) |
| Release notes | SPFx releases — replace 1.23 with target version (1.22, 1.21, …) |
| Repo | github.com/SharePoint/spfx (open since 1.23) |
Wait for GA. Preview risks drifting scaffold pins.
Release notes — what matters
| Signal | Impact |
|---|---|
| New component type (e.g. Copilot Apps) | New manifest shape, possibly new packaging elements |
| Schema changes | Check SPFx schemas and write-manifests schema |
| Deprecations (e.g. 1.23 workbench) | Dev URL, not bundle format |
| Toolchain shift (Heft since 1.23) | Official scaffold shape, not RSPFx format |
Harvest (core methodology)
- In scratch dir
npm packthe official packages at target version:@microsoft/spfx-heft-plugins,@microsoft/sp-build-web,@microsoft/sp-webpart-base, and all@microsoft/sp-*runtimes. - Extract: component IDs/
version/preloadComponentsfromnode_modules/@microsoft/sp-*/dist/*.manifest.json; formats (manifest builder, AMD wrapper,manifests.js,.sppkglayout, workbench URL) from build plugins. - Record provenance in
reference/FORMATS.mdper section (exact versions harvested). - On discrepancy verify against an unzipped official
.sppkgbefore changing code.
Diff & update references
Compare IDs, manifest schema, bundle wrapper, manifests.js, .sppkg layout, package-solution.json semantics vs current. Update reference/FORMATS.md, reference/sp-component-ids.json, and its compiled copy packages/manifest-generator/src/data/component-ids.ts in sync.
Add version
{ target: '1.24', npmVersion: '1.24.0', toolchain: 'heft', status: 'ga' }Fields: target (user-facing), npmVersion (scaffold pin), toolchain (informational), status (ga/preview). Downstream updates are automatic.
Tests & docs
Tests: core default assertion, template dep pins, CLI new/config flag values. Then bun run typecheck && bun run test.
Docs to update:
| File | What |
|---|---|
compatibility.md | Version matrix |
commands.md | --spfx-version flag |
why-not-to-migrate.md | Supported row |
ARCHITECTURE.md | Non-negotiables if formats changed |
History lives only in CHANGELOG.md.
Verify
rspfx new --spfx-version <v> --yes && rspfx package- Bundle header starts with capture line then
define('<id>_<version>', …) - Diff
.sppkgentry list vs harvested reference - Real-tenant install (app catalog → site → workbench)
- Blackbox parity:
bun run test -- --run packages/sppkg-builder/tests/blackbox(fast, RSPFx invariants only) orOFFICIAL_SPPKG_TEST=1 bun run test -- --run packages/sppkg-builder/tests/blackboxwithOFFICIAL_SPPKG_VERSIONS=1.22,1.23,1.24for full RSPFx vs official ZIP comparison (seepackages/sppkg-builder/tests/blackbox.test.ts:1andbench/blackbox-compare.mjs:1). The harness treats both generators as external ZIP producers and normalizes volatileAppPartConfig Id/Extension Instance Idbefore assertingAppManifest.xml,[Content_Types].xml,feature_*.xml,<featureId>/*_*.xml, and entry-list equality.
Checklist
Gotchas
| Gotcha | Detail |
|---|---|
| Never re-publish yanked version | npm E400 after unpublish — bump instead |
examples/* stay on 1.22 | Intentional matrix coverage, don't "fix" |
| Workbench retiring Dec 1 2026 | Debug Toolbar replaces it — affects dev URLs |
| Harvest from spfx-cli repo when freshest | Since 1.23 it's open source |
Tip: after harvest, unzip an official
.sppkgand byte-comparedefine('<id>_<version>'header and zip entry order — visual diff misses layout bugs.