#spfx 154 articles
"The requested permission isn't valid" in API access — the API registration doesn't exist yet gotchas / spfx API access resolves resource to a service principal, so the app registration must exist before the .sppkg is uploaded "User:… dont have any permissions" from the audit query API rejects the USER, not the app gotchas / graph Consent vs. Exchange audit role; Global Admin usually inherits it, so compare accounts /_api/hubsites is trimmed per user — a lookup that walks the hubs finds your app for the page editor and not for the reader gotchas / rest-api GET /_api/hubsites returned 2 hubs to an admin and 1 to an ordinary user who had Full Control on the missing hub's site, so client code that discovers something by walking the hub list works for the editor and silently… /me/sendMail from an account without a mailbox can time out with 504 – and the Graph SDK retries it three more times gotchas / graph No MailboxNotEnabled error, just a 504 after ~30 s; the SDK RetryHandler repeats the non-idempotent POST up to 3×, so the button hangs ~2 min and a late success can send duplicates; check assignedPlans for an enabled… behavior: 'smooth' is silently ignored inside a portaled overlay gotchas / spfx Jump-to-section does nothing in a modal/reader; drop the flag and set scrollTop directly ensureuser returns the login name — stop looking it up by Email gotchas / rest-api The response already carries LoginName; a follow-up siteusers filter on Email misses accounts whose UPN differs For license information please see … leads to a 404 — the publish step left the bundle's .LICENSE.txt behind gotchas / tooling webpack moves third-party license comments into .js.LICENSE.txt and leaves only a pointer in the bundle; a publish step that copies *.js, or an allowlist .gitignore, ships the code without the texts MIT and BSD… global.navigator = dom.window.navigator is ignored on Node 21+ (or throws) — your jsdom test runs with Node's navigator gotchas / tooling Node 21 added a global navigator that has a getter and no setter, so the classic hand-made jsdom setup line is silently ignored (strict mode throws a TypeError) and the test sees Node's userAgent (e.g. "Node.js/24")… POST /views takes an SP.View body — ViewTypeKind fails, and @odata.type passes only under OData v4 gotchas / rest-api ViewTypeKind from the creation-information docs 400s, and an @odata.type annotation passes only with OData-Version: 4.0; measured recipe for view + scope + column order Promise.all discards the soft reads it was waiting for — one failing main read leaves every picker loading forever gotchas / spfx A failing main read rejects the batch and the soft reads' results and failure flags never reach the pickers; settle each read on its own Retry-After does not survive CORS — a header you send and the client reads can still arrive as null gotchas / azure-functions A cross-origin response exposes only the headers named in Access-Control-Expose-Headers plus a short safelist, so an API that sends Retry-After and a client that reads it still meet at null — both ends look right and a… speechSynthesis.cancel() does not stop chunked reading — it queues the next chunk gotchas / spfx The utterance chain re-queues itself from onend; epoch counter bumped BEFORE cancel(), and no onEnd from a superseded chain stopPropagation in onClick does not stop the keyboard — a clickable row swallows Enter and Space of the buttons inside it gotchas / spfx A clickable row with onKeyDown also receives the Enter/Space of its inner buttons and checkboxes, so their action is cancelled (or runs twice) and the detail opens; react only when e.target === e.currentTarget TextEncoder is not defined before your first test runs — a dependency needs it at import time and jsdom does not have it gotchas / tooling jsdom has no Encoding API and a dependency that builds new TextEncoder() at module level dies on import, before beforeAll; import a polyfill module first or use setupFiles, and make the dependency lazy window.scrollTo does nothing in an SPFx web part on a modern page gotchas / spfx The page scrolls inside a canvas element, not the window; switching views in a single-page web part leaves the user mid-page. Find the real scrolling ancestor and reset that A "grant it for me" button cannot work in a web part — not even for a Global Administrator gotchas / spfx A delegated call gets the intersection of the user's rights and the app's grants, so the admin at the keyboard adds nothing; writing the grant would take at least DelegatedPermissionGrant.ReadWrite.All approved for the… A batch file move must be grouped by file, not by the record that points at it gotchas / rest-api When records share a file URL, iterating records moves it once and 404s on the rest; group by file, write the new URL to every record, and adopt a file already at the target on a rerun A CDN-hosted extension bundle may only import modules its App Catalog manifest declares gotchas / spfx Replacing an extension's stable-named bundle on the CDN without a new .sppkg works only while its define([...]) lists modules already in the installed manifest's scriptResources — one new value import and the extension… A CDN-hosted SPFx bundle still needs a new .sppkg gotchas / spfx includeClientSideAssets: false moves assets, not versioning; the manifest pins a content-hashed filename, so old packages never request the new bundle A command set's commands are fixed in its manifest gotchas / spfx title, iconImageUrl, visible and disabled change at runtime, new commands need a new .sppkg; ship one entry that opens your own menu, or reserve hidden slots (and keep their titles honest) A component from a linked package throws "Invalid hook call" in Jest — the test loaded a second copy of React gotchas / tooling Jest resolves a file:/junction package by its real path, so the package's require('react') finds a second copy; map react and react-dom to the app's copy (the SPFx build is unaffected — React is a platform component) A conditional spread hides a phantom column until SharePoint returns 400 gotchas / spfx ...(cond ? {…} : {}) bypasses the excess-property check, so a field the list doesn't have reaches the POST and 400s; two scripted sweeps find the rest A deleted or moved test keeps running from stale lib-commonjs — the SPFx rig's --clean never touches that folder gotchas / spfx In SPFx projects built with Heft, Jest runs the compiled tests in lib-commonjs, but the rig's clean step deletes dist, lib, release, generated .scss.ts/.resx.ts files and parts of temp (and jest-output for the test… A docked side panel floats in mid-air on the newer SharePoint layout: .sp-App-bodyContainer is not there gotchas / spfx A Copilot-style side panel narrows the SharePoint page by shrinking .sp-App-bodyContainer. On the newer page chrome (left app bar with Discover/Publish/Build) that container does not exist, so the docking code silently… A file-size limit doesn't stop a zip bomb — Office extraction needs a decompressed cap gotchas / spfx An upload-size limit only bounds the compressed archive; a crafted !ref still OOMs the tab A global kill-switch must not block the operation that fixes it gotchas / spfx The lock defends itself against being lifted; infrastructure operations (version pin, re-check, config, diagnostics) belong outside it A Graph scope your SPFx solution never asked for may already work — grants are tenant-wide gotchas / spfx webApiPermissionRequests is a request, not a grant; an approval lands on one tenant-wide principal, so a scope another solution had approved already works in yours (and may be missing at the customer) A library component loaded from your own manifest fetches its lazy chunks from that manifest's folder gotchas / spfx SPComponentLoader.loadComponent + a relocated manifest: without the chunk.* files next to the bundle every import() 404s on first use, which an open-the-app smoke test never exercises A list's ETag tracks its schema, so ItemCount comes back stale via 304 gotchas / rest-api Adding items never moves the list entity's ETag, so revalidation answers 304 and the browser serves the body from when the list was empty; affects entity reads (GetList, lists(guid)), not collections or /items A mailto: fallback that reuses the success state tells the user their message was sent when it wasn't gotchas / graph sendMail fails on accounts without a mailbox and the fallback only hands a draft to a mail client; model sent / handed off / failed, never a boolean A module-level cached dynamic import() caches the rejection too gotchas / spfx One chunk-load blip poisons the module-level promise for the whole session; reset it to null in .catch so the next call retries A network drop through the Microsoft Graph JS client looks like an HTTP error with status -1 gotchas / graph msgraph client wraps a failed fetch as GraphError statusCode -1, code TypeError – detect it, don't report "permission denied" A newsletter looped over /me/sendMail is not bulk e-mail — and ACS Email is no longer the way out gotchas / graph Per-mailbox and tenant limits, bulk mail through Microsoft 365 "isn't a supported use", JSON sendMail can't set List-Unsubscribe, ACS Email retires 2028; keep Graph for personal batches, send newsletters through an ESP… A parser branch whose entry condition accepts more than its collector loop consumes will freeze the tab gotchas / spfx A branch that accepts 1) but collects only 1. spins forever on one line until out-of-memory; one shared regex, a no-progress contract and an outer backstop, each sufficient alone A Promise wrapped around img.onload can hang forever — try/catch won't save you gotchas / spfx onload/onerror are not guaranteed to fire; try/catch guards rejection, not a Promise that never settles A protocol-relative URL walks straight through your host allowlist gotchas / spfx A host check built on ^https?:// reads //evil.example/… as relative and lets it through; the browser then supplies the scheme and the request leaves for a foreign origin A provisioning step gated on success runs forever for every non-admin gotchas / spfx A startup step needing ManageLists writes no marker for ordinary members, so the whole batch replays on every page load; the marker needs two states A read-only mode that wraps SPHttpClient still sends e-mail — Graph is a second channel gotchas / spfx An SPFx app writes through two clients; wrapping SPHttpClient leaves mail, calendar and Planner wide open A screenshot pasted into a support form is too big for /me/sendMail — shrink it client-side gotchas / graph Inline base64 attachments share a ~4 MB request budget; shrink bitmaps in a canvas, cap the total, and remember mailto: fallbacks carry no attachments at all A server-side origin check rejects your own smoke test — and the runbook that says "this bypasses CORS" gotchas / azure-functions Once the allowlist is enforced in the handler it applies to every caller, so the deploy script's own verification and the runbook command both need the Origin header; "this test bypasses CORS" stops being true and… A setup marker that measures only part of the step freezes a half-provisioned site forever gotchas / spfx The success flag measured the id lookups, not the addroleassignment POST, so a 403 froze a site with groups that carry no permission level at all A shared component that avoided positioning assumptions still carried a width assumption gotchas / spfx @media never sees the narrow panel you were reused in; measure the element with ResizeObserver and switch every child together A shared component's var() fallback chain is only as good as its last link gotchas / spfx Shared UI package falls through to sans-serif in apps that never publish CSS custom properties; runtime-only, no build catches it A shared package's dynamic import() ships inlined (not lazy-chunked) under module:commonjs gotchas / spfx A linked TS package built with module:commonjs turns import() into require(), so webpack can't lazy-chunk the lib into a separate file; set the package's module:esnext A shared stylesheet injected once per page keeps the version that rendered first — your CSS fix may not apply next to an older build gotchas / spfx A library that injects only if the id is missing lets whichever bundle renders first decide the CSS for every web part on the page, so a fixed rule in a newer bundle can silently lose to an older copy that rendered first A silently-failed read turns reconciliation into delete-everything gotchas / rest-api A quiet if (!ok) break turns "sync what changed" into "delete everything"; fail-safe for a view means show what you can, for a delete it means do nothing A Teams message your app posts in the user's name doesn't notify that user — and "I can't see it" needs a read-back, not a guess gotchas / graph Delegated ChatMessage.Send delivers (even to the self chat 48:notes), but Teams never alerts the sender; verify with GET + webUrl, notify from the app A test that runs under a different command than your release guards nothing gotchas / spfx heft build runs no Jest and the two prebuild scripts drift, so a rule can look covered while never executing on the release path A textarea jumps after you change its text from code and restore the selection gotchas / spfx focus() scrolls the page to the element and, in Chromium, setSelectionRange scrolls the textarea to the end of the selection; restore the selection with focus({ preventScroll }) and put scrollTop back A theme override on your app root can't reach tokens declared on :root gotchas / spfx The accent inherits, everything derived from it doesn't; substitution happens where the token is declared, portals miss the override entirely, and a colour written as a literal hex is unreachable either way A throttled permission probe must not downgrade — and cache — the role gotchas / rest-api A 429/403 on currentuser/groups resolves to the lowest role; cache it and the user is stuck read-only for the TTL; only persist a confirmed (200) result A trailing-whitespace regex freezes the page when it parses AI output gotchas / spfx /\s+$/ and lazy (.+?)\s*$ are quadratic on a line with a long run of spaces that does not end the line; text from an AI model or a foreign document is untrusted input, so trim with trimEnd() and anchor captures on a… Actions must go before the alias query string, not after the URL gotchas / rest-api Appending /breakroleinheritance(...) to a GetList(@u)?@u=... URL buries the action inside the parameter — splice it in before the ? Adding or removing a group member via Graph needs the object ID — a UPN returns 400 "Invalid object identifier" gotchas / graph POST /groups/{id}/members/$ref and DELETE /groups/{id}/members/{id}/$ref accept only the user's object ID (GUID); a UPN from a people picker returns 400 Invalid object identifier — resolve it with GET… An allowlist sanitizer that keeps the text of disallowed tags dumps CSS and JavaScript source into your content gotchas / security An allowlist needs two sets: tags to keep, and tags to drop WITH their subtree; otherwise a pasted web page dumps its stylesheet into the article as visible text An auto-save effect computed from async-loaded inputs writes garbage on mount — gate it on a loaded flag gotchas / spfx Computed-from-async auto-save fires on mount with empty inputs and persists garbage; gate on a loaded flag An ECDSA signature made in Node fails crypto.subtle.verify in the browser — Node defaults to DER, WebCrypto expects raw r||s gotchas / tooling crypto.sign in Node returns a DER-encoded ECDSA signature (69–72 bytes); WebCrypto verify in an SPFx bundle expects IEEE P1363 (64 bytes for P-256) and just returns false. Sign with dsaEncoding ieee-p1363, check the… An element selector outranks your button class — and only a themed tenant sees it gotchas / spfx .app-root a { color: inherit } beats .btn--accent, so link-styled buttons get an unreadable label — but only on a tenant whose accent needs white text An image upload that accepts image/* accepts SVG — and SVG is a script gotchas / security SVG is a script: inert inside , executed when the file's own URL is opened; allow-list raster MIME types An overwritten script with a stable name keeps serving the old code gotchas / spfx max-age=600 on GitHub Pages, a memory cache shared by same-site tabs and SharePoint's service worker; keep stable files thin, test with cache: 'reload' and every tab closed, and don't read transferSize of a… An SPFx chunk's file name is not its content — realContentHash is off gotchas / spfx SPFx builds with realContentHash: false, so a byte-identical chunk can get a new name (measured) and nothing rules out the reverse; deduplicate by bytes or git blob and never overwrite a published file An SPFx web part that calls an intranet API makes Chrome and Edge ask every user for local network access gotchas / spfx A *.sharepoint.com page fetching a host that resolves to a private IP is a local network request – Chrome (from 142) and Edge prompt every user and a refusal is a bare Failed to fetch; pre-allow the SharePoint origin… An unparseable JSON column is not an empty column — read-merge-write wipes the record gotchas / spfx catch → {} turns "could not read" into "is empty" and the next save writes the emptiness back over the record; keep three states, refuse the write before the first PATCH, lock the form visibly, and make recovery an… Approving Microsoft Graph permissions for SPFx needs a Global Administrator — Application Administrator is not enough gotchas / permissions API access requests for Microsoft Graph need Global Administrator (Application Administrator only covers third-party APIs), and admin consent for Graph application permissions such as Sites.Selected is explicitly… Audio in an SPFx web part (SharePoint and Teams): playback may be refused, the feature must not stop gotchas / spfx SPO's CSP has no media-src today, so MP3 from your CDN plays; start playback from a click and fall back to silent timing on NotAllowedError, 404 or mute Building a people picker in SPFx — the endpoints that actually work gotchas / spfx SP Search People source + ensureuser; why the obvious endpoints fail Calling SharePoint REST like a pro guides The client landscape, headers that matter, safe writes, reading well, field/list creation quirks, and a ten-minute diagnosis routine CDN pruning deleted the extension bundle the deployed .sppkg still referenced gotchas / spfx With includeClientSideAssets: false the catalog pins hashed names for customizers and command sets too; a "keep N newest" prune evicts the referenced hash and the extension 404s for days — protect extension bundles by… Creating a modern page via REST is a three-step dance, not one POST gotchas / rest-api CanvasContent1 won't stick on create; create → SavePageAsDraft → Publish, canvas is JSON; plain JSON is enough, Description needs its own write and a re-publish; the file name is random — rename with moveto Creating a team right after creating its Microsoft 365 group returns 404 — retry only the 404, and don't log the failure as success gotchas / graph PUT /groups/{id}/team fails with 404 for minutes after POST /groups (replication); retry only 404, surface a real failure as a warning CSV export of list data hands Excel a formula, not text gotchas / security A member-written cell starting = + - @ (or TAB/CR) runs in Excel on the reader's machine; quoting doesn't disarm it, an apostrophe prefix does DecompressionStream throws away the whole result when bytes follow the compressed data gotchas / spfx Inflating a PDF FlateDecode stream in the browser fails completely because a line break sits between the data and endstream; trim trailing CR/LF and read chunk by chunk, keeping what arrived before the error Deep-link parameter appended to a SharePoint page URL that already has one gotchas / spfx The address an admin pastes already carries your routing param, so appending makes it appear twice and URLSearchParams.get reads the OLD value; replace the key, never append Diagnostics that cannot survive the crash they are diagnosing gotchas / tooling Frozen threads stop repainting, sessionStorage dies with the tab and the next healthy run overwrites the crash report; persist synchronously and never gate recovery on a flag Driving an SPFx page from the console — two traps that fake success gotchas / tooling input.value from the console leaves React state empty (use the native setter + input event) and window.confirm freezes CDP automation; both fail silently as success Drop __metadata from write bodies — odata=verbose needs OData v3, and SPHttpClient sends v4 gotchas / rest-api Old-tutorial payloads 400 as plain JSON, and switching to odata=verbose 400s again under SPHttpClient, which sends odata-version: 4.0; write plain JSON without the type hint, or blank odata-version where the verbose… Dropping a folder onto your upload zone does nothing (dataTransfer.files never sees it) gotchas / spfx dataTransfer.files cannot see a folder, items is neutered after the first await, and readEntries() returns the contents in batches of 100 Exchange Online Admin API (the REST behind EXO V3) cannot be called from the browser — no CORS, and SPFx tokens lack Exchange.Manage gotchas / graph adminapi/beta/{tenant}/InvokeCommand fails with 'Failed to fetch' even with a fake token; read Exchange config with an admin-run script and import, or use Graph message trace and mailbox reports FieldValuesAsText re-encodes underscores in JSON keys gotchas / rest-api A_x0020_B comes back as A_x005f_x0020_x005f_B; decode _x005f_ before matching File upload via /Files/add: nometadata works under OData v4 — an empty OData-Version breaks SPFx when you leave out Content-Type gotchas / rest-api nometadata + octet-stream under the default OData v4; an empty OData-Version without Content-Type throws inside SPHttpClient; plus the empty-filename mobile camera trap Gallery cards render from tileProps, not from formatter gotchas / lists The documented top-level formatter is ignored; the card lives in a nested, undocumented tileProps.formatter. Layout and card must go in two separate writes (PnP or REST/SPFx) Get lists by URL, not by title gotchas / rest-api getbytitle() breaks the moment someone renames a list; resolve by URL (bonus: GetList normalises folder paths to their list) GetStorageEntity returns 200 for a missing key, not 404 gotchas / rest-api Unset tenant property answers 200 {"odata.null":true}; "unset" and "failed" are indistinguishable by status code, and writing needs Tenant Admin — same trap in …?$select=Exists, which returns 200 {"Exists":false} Graph usage reports in the browser — the v1.0 CSV redirect is CORS-blocked, beta JSON works gotchas / graph v1.0 302 to CSV dies on CORS; beta ?$format=application/json returns 200 from Graph; mind roles and concealed names Hand-built .docx / .pptx: the parts Office demands even though they do nothing gotchas / spfx Header images are referenced from the header's own rels, Content_Types must know the image extension, namespaces are per part, and a .pptx without theme + master + layout is invalid; split parts from zipping so a… Hiding a field in an SPFx web part is not a permission gotchas / security Role-based UI field hiding is cosmetic; Read on the list = REST/Export/other web parts see it; confidentiality needs a separate list, item perms, or a server tier — plus the two traps the separate list creates: a… Importing third-party CSS with url(images/...) breaks the SPFx build gotchas / spfx url(images/...) without ./ kills the build; inject a instead In a Heft/SPFx project jest.mock() doesn't hoist — and sp-http drags in a missing MS-internal module gotchas / spfx Tests run over pre-compiled lib-commonjs without Babel, so a mock written below the imports lands after require; write it above the imports or use moduleNameMapper (and why only some sp-http suites die on… Injecting your own column into the modern list grid — rows are display:contents, cells carry explicit grid placement gotchas / spfx An appended cell lands in the wrong row and shifts the table; rows are display: contents, so the cells are the grid items and every one carries explicit grid-column/grid-row Jest's 5-second default timeout fails render tests under load, and the timed-out test breaks the next one gotchas / tooling The first jsdom + React test of a suite pays for the cold module load; on a busy build machine it passes 5 s, Jest abandons it while its async render keeps running, and the following test fails with "button not found"… justify-content:center inside overflow:hidden clips content you can never scroll to gotchas / spfx Centering + overflow cuts content above the scroll; flex "springs" List and library pages run a stale extension bundle from Cache Storage for days gotchas / spfx Stable (unhashed) bundle URL = list and library pages can run a days-old copy from the Lists app's own Cache Storage (Lists-odsp-web-prod_*), whatever Cache-Control says; keep the stable file a tiny loader and load… ListViewCommandSet button never appears — raiseOnChange() does not re-run onListViewUpdated gotchas / spfx Two independent causes: uploading a new .sppkg registers the extension on no site by itself (register it at runtime, or deploy it tenant-wide with the all-sites tick), and raiseOnChange() re-reads command.visible… Load an SPFx bundle's lazy chunk from the browser console — no clicking through the UI gotchas / spfx Push a fake chunk with a runtime callback into the bundle's webpackJsonp_ array to get its real __webpack_require__, then req.e(chunkId) loads the chunk exactly as the app would — for checking 404s, SRI integrity or… Manifest SRI does not cover scripts your SPFx bundle loads itself (pdf.js, tesseract.js, workers) gotchas / spfx Pinning the loader in the .sppkg and giving every manifest path and webpack chunk an integrity hash still leaves a hole – third-party libraries your own code injects with a script tag, fetches for a worker or that a… Mermaid clips node text when you set a web font (measure-before-load) gotchas / spfx Measure-before-load (FOUT) sizes boxes for the fallback font; use a system font stack Message Center via Graph — with $orderby you may get one page and no nextLink; page with $skip gotchas / graph messages?$top=100&$orderby=… returned 100 posts without @odata.nextLink; $top + $skip returned all 441 Minified React error #310 (and friends) — the SPFx debugging cheatsheet gotchas / spfx #310/#300/#321/#31/#185 decoded for SPFx debugging npm audit --omit=dev overstates what SPFx actually ships gotchas / spfx SPFx keeps its Node build toolchain in dependencies, so 229 „production“ findings can hide a single real one; grep the published bundle and always include a control sample Opening a pre-filled e-mail — with an attachment — in the user's desktop Outlook gotchas / spfx No API tells you which mail client is default; mailto: reaches it but carries no attachment, and a .eml needs X-Unsent: 1 or it opens read-only pageContext.cultureInfo returns the web language sometimes and the user language other times gotchas / spfx The same page loads English once and Czech the next time; cache the resolved language per web instead pageContext.site.serverRequestPath is not the page URL — it can hand you a REST endpoint gotchas / spfx It is the last SERVER REQUEST path, so a modern list view can hand you …/RenderListDataAsStream; validate the shape before storing it as your app page pdf.js: "Cannot use the same canvas during multiple render() operations" on resize gotchas / spfx A React effect that re-renders a PDF page on every resize starts a second render() before the first finishes; cancel the running task in cleanup and chain renders so they never overlap Per-user data vanishes for admin accounts: pageContext.user.email is empty and eq '' matches nothing gotchas / spfx Mailbox-less accounts get an empty pageContext.user.email; rows save under an empty key, SharePoint stores empty text as NULL, and $filter=Field eq '' never matches it again Polling the manifest proves nothing — a static host serves the small file first gotchas / spfx A static host publishes the small manifest before the large entry bundle, so the component loads nothing and logs nothing; and an A/B against the previously deployed version compares deploy age, not code (+ a 503 on… Portaled overlays sit outside your CSS reset — hello, phantom scrollbar gotchas / spfx Portal to body escapes the scoped reset; content-box fields overflow and grow a scrollbar position:fixed dropdowns go off-screen inside animated panels — the CSS transform trap gotchas / spfx The CSS transform containing-block trap; portal to document.body Power Automate admin flows API returns UserImpersonationScopeMissing from SPFx – even after you approve the scope gotchas / spfx SharePoint's token for Microsoft Flow Service carries only Microsoft's preauthorized scopes, so an approved User grant never reaches the token and the tenant-wide flows list stays 401 Power Platform admin API returns nextLink on a regional host – a guard that only knows api.powerapps.com breaks paging for US tenants gotchas / rest-api Admin listings page through unitedstates.api.powerapps.com (and other one-label regional hosts); a token guard that allows only the global host rejects page two, and you never see it on a small tenant – allow exactly… Previewing library files in your own app: every format behaves differently gotchas / spfx PDF embeds directly, Office needs WOPI by {UniqueId}, HTML is served as an attachment and never renders from src; SVG and HTML must never be framed from your own origin (uploader script runs in the viewer session) and… Printing from a web part gives a blank page when the print CSS hides body > * and the print layer lives inside the web part gotchas / spfx One way to print a report from an SPFx web part is to hide everything in print (body > * { display: none }, so the SharePoint suite bar, navigation and footer stay out) and show only a dedicated print layer. If that… Purview audit query API — a query for a service you have no scope for still "succeeds" with 0 records gotchas / graph With only AuditLogsQuery-SharePoint.Read.All, Exchange and Entra queries return 201 → succeeded → 0 records; an unfiltered query returns only SharePoint records Reading the modern list selection from the DOM — names only, and the count lies gotchas / spfx Only a ListViewCommandSet gets selectedRows; from the DOM take names only (the name is a button, not a link), resolve paths with one folder-scoped call, and take the real count from clearSelectionCommand because the… Retry only fast failures — a gateway error after 230 seconds is a timeout, not a hiccup gotchas / azure-functions The 502/503/504 a client rightly retries for transient failures also comes back when a request ran out of time (Azure Load Balancer returns 502 after 230 s), and retrying that multiplies the wait (3 × ~230 s) and the… Retrying a throttled call is safe for GET only gotchas / rest-api 429 means "not now", not "nothing happened"; no idempotency key exists, so a retried write duplicates the row (+ RateLimit-* headers are not guaranteed for delegated calls) Reverse-engineering Plumsail Forms apps for migration or audit guides Where the JSON definitions, custom JS, and hosting web parts live; a read-only extraction routine and the client-side patterns (dummy-item pages, UI-only security, race conditions) to flag before a migration Rolling out required tokens on your own API: the "calls without a token" counter measures every client, not just the app you fixed gotchas / spfx You add Entra ID token validation to an API your SPFx solutions call, ship it in soft mode and wait for "calls without a token" to reach zero — it never does, because a shared npm package, a second solution, a… rules-of-hooks false-positive — a complex JSX && chain flags the wrong hook gotchas / spfx A complex conditional in your render blames the wrong hook; extract it to a const. Second trigger: sheer component size — there extracting conditions doesn't help, splitting the component does. Third trigger: the… Search REST API needs odata-version 3.0 gotchas / rest-api The header behind mysterious search 500s SharePoint's built-in PDF conversion endpoint works only with a plain fetch — auth headers break the redirect gotchas / rest-api _api/v2.0/drives/…/content?format=pdf converts Office files server-side with no Graph consent, but auth-decorating clients break its 302 to *.svc.ms; resolve the drive explicitly and guard on Content-Type SharePoint's SPA router hijacks anchor clicks — onClick never fires gotchas / spfx navigates before React onClick runs; use buttons for in-app actions Sharing text through a URL (Teams /share, mailto) breaks at length limits gotchas / spfx "Send to Teams" (/share?msgText) dies with AADSTS90015 on long answers and mailto silently won't open; cap the URL payload and put the full text on the clipboard SP.WebProxy is add-in-only — from SPFx it 403s with "without an app context" gotchas / spfx No SharePoint-native CORS proxy for SPFx; the 403 hides inside an HTTP 200 SPFx adds Subresource Integrity to a script when the manifest path is { path, integrity } gotchas / spfx A component manifest you pass to SPComponentLoader.loadComponent can carry an SRI hash per script resource as path { path, integrity }; SharePoint then emits script integrity crossorigin=anonymous and the browser… SPFx build fails on padStart, includes, Object.values — the ES2015 lib trap gotchas / spfx TS2550 on padStart & friends, and the safe equivalents SPFx in Teams — a personal app needs global deployment, not a root-site install gotchas / spfx skipFeatureDeployment: true + "all sites"; the teams/ icon-folder convention SPFx in Teams — the current user's numeric ID belongs to the host site, not to your data site gotchas / spfx legacyPageContext.userId in a Teams personal app is the tenant root site's ID; written as the user's ID on another site it can silently point to someone else SPFx MSGraphClient calls don't show in DevTools Network — and how to diagnose them anyway gotchas / graph SPFx Graph traffic invisible in Network; diagnose via performance entries, currentuser, DOM, user's response Stored XSS via SharePoint list content — React won't save you gotchas / security React doesn't block javascript: hrefs; allowlist safeHref with C0-strip at every sink Teams mobile webview renders your tab at ~980px — and you can't reproduce it in a browser gotchas / spfx ~980px layout; fix the viewport meta in Teams first The browser cache answers your read-after-write check — with a 200 gotchas / rest-api The same URL read on both sides of a write is served from the browser cache, so res.ok guards pass on stale state; a cached existence check makes POST /fields create a duplicate column and report success, and an… The ETag key is @odata.etag in OData v4 – reading odata.etag silently turns IF-MATCH into * gotchas / rest-api SPHttpClient speaks OData v4: the item ETag comes as @odata.etag; reading odata.etag yields undefined and your IF-MATCH protection quietly disappears The hosted workbench restores your web parts on the next site you open in the same tab gotchas / spfx The hosted workbench keeps its canvas in sessionStorage per tenant origin, not per site; open another site's workbench in the same tab and your web part boots there — with that site's pageContext, so first-run… The Microsoft Graph JS client sends $filter values unencoded — & splits the query, # cuts it off gotchas / graph .filter(), .query() and .api('…?…') pass the value into the URL as-is (SDK 3.0.2, also under SPFx MSGraphClientV3): # cuts the query, & splits it and .api() even moves $top into the filter; double apostrophes, then… The public Microsoft 365 roadmap JSON has no CORS headers — an SPFx web part can't fetch it gotchas / spfx releasecommunications/api/v1/m365 answers 200 without Access-Control-Allow-Origin; proxy it server-side The tenant's CSP is inherited by srcdoc frames — inline scripts in embedded HTML never run gotchas / spfx Modern pages send script-src without 'unsafe-inline', and a document you build yourself (srcdoc, blob:, data:) inherits it, so every inline script in the embedded HTML is dropped; an external file from an allow-listed… There is no sitegroups/removebyname — delete a group by Id gotchas / rest-api GroupCollection has GetByName but only RemoveById; resolve the Id first, then delete; verify with getbyname → 404 Three .sppkg packaging pitfalls — diacritics, icon size, the Publisher column gotchas / app-catalog ASCII-only solution name, icon exactly 96×96, Publisher column is AppSource-only Turn a library URL someone pasted into web + library + folder snippets / rest SP.Web.GetWebUrlFromPageUrl, called on the CURRENT web, resolves the web of any URL in the tenant (other site collections too, 404 for a missing site); strip Forms/…aspx, prefer the ?id= folder, take the first segment… Two "Uncaught (in promise)" from sp-pages-assembly on a modern page are SharePoint's own, not your web part gotchas / spfx On SharePoint Online modern pages the console can show two "Uncaught (in promise)" exceptions with reason undefined and a stack only inside sp-pages-assembly, on every load and on SPA navigation – they also appear on a… Two MERGEs to the same list item at once: the second one fails with 409 Save Conflict even with IF-MATCH: * – and a fire-and-forget write just disappears gotchas / rest-api IF-MATCH: * only switches off the ETag check (412); SharePoint still refuses a second save of the same item while the first is in flight with 409 Save Conflict. Queue writes per item (list#id), retry 409 briefly, never… Typographic quotes in JSX attributes break the parser (TS1003) gotchas / spfx Typographic quotes in attributes = TS1003; wrap as {'…'} Unbounded Promise.all fan-out over a customer-sized collection invites HTTP 429 gotchas / rest-api Fan-out is safe when YOUR code decides the count, dangerous when the customer's data does; bounded batch and worker-pool patterns Upload a generated image as a list item attachment (no file picker) snippets / rest Canvas charts, placeholder banners, QR codes — AttachmentFiles/add takes a raw ArrayBuffer; the item must exist first Version-gated provisioning silently drops the settings only an admin could write gotchas / lists A settings PATCH needs Manage Lists; the first member to open the app after an upgrade burns the one attempt and the version is stored anyway Web part properties are missing from CanvasContent1 on single-part app pages gotchas / rest-api Single-part app pages store web part config elsewhere; do not diagnose "not set" from page fields — but the web part id IS there, so use it to find the hosting page (not ClientSideApplicationId) webUrl + a server-relative URL doubles the site path gotchas / rest-api Survives for months as a display value or grouping key and breaks the first time it becomes a filter; build object URLs from the origin in one helper window.open with noopener returns null even when the tab opened — so your fallback runs every time gotchas / spfx Code that reads null as "blocked" runs its fallback every time: the e-mail opens twice (Outlook on the web and mailto:), the print window stays empty; open without the feature and set w.opener = null Wrapping MSGraphClientV3 to count calls fails silently — and your fail-safe catch hides it gotchas / spfx Reassigning client.api throws and your fail-safe catch hides it; use Object.create and assert it attached X-RequestDigest expires ~30 min after page load — but adding it by hand in SPFx makes things worse gotchas / rest-api Writes 403 "security validation is invalid" on a long-open page; raw fetch needs a fresh digest from /_api/contextinfo, but in SPFx a hand-set header switches SPHttpClient's own digest and 403-recovery OFF Your Application Customizer runs again inside SharePoint dialog iframes gotchas / spfx A floating button rendered twice, the copy pinned to the dialog's corner; refuse nested browsing contexts before anything else (+ un-render path and why a body MutationObserver needs a definitive „no“) Your Application Customizer's floating UI disappears when navigating the site gotchas / spfx The button vanishes as you click around the site; visibilitychange never fires on in-page nav, so re-render on navigatedEvent (+ a MutationObserver backstop that stops re-reading configuration after a definitive… Your URL sanitiser strips spaces — and every SharePoint file link 404s gotchas / security The C0 strip that blocks javascript: also eats the plain space, so every link to /Shared Documents/… 404s; strip to decide, return with %20 Your zip-bomb guard reads a private JSZip field — and fails open on the next upgrade gotchas / spfx entry._data.uncompressedSize is an implementation detail, and undefined → allow turns the next library upgrade into a silent loss of protection; read the declared sizes from the ZIP central directory instead