What the new Office.js CDN endpoint actually changes
I run silentship, an independent monitor for changes on the Office.js CDN. This is a copy of the original note.
Since late September, Microsoft's docs list appsforoffice.microsoft.com as a legacy endpoint and recommend a new one:
| | Legacy | New |
|---|---|---|
| Release | appsforoffice.microsoft.com/lib/1/hosted/office.js | officeapis.public.onecdn.static.microsoft/1/office.js |
| Preview | appsforoffice.microsoft.com/lib/beta/hosted/office.js | officeapis.public.onecdn.static.microsoft/beta/office.js |
The docs say the new endpoint "serves the same current Office.js release, but it enables additional secure defaults", without saying which defaults. So we compared the two directly.
Short answer
- Same build. Both endpoints serve 16.0.20606.15070.
- Host files are byte-identical. excel-win32-16.01.js, word-win32-16.01.js, outlook-win32-16.02.js, powerpoint-web-16.00.js and the rest have the same SHA-256 on both. Every Office API your add-in calls comes from the same code.
- Only office.js (the loader) differs, and only by two lines at the top:
isOsfSecurityDefaultsEnabled = true;
osfLoadMsAjax = false;
Everything else in the loader is identical. Both endpoints also sit behind the same Akamai + Azure Front Door setup, with the same caching headers.
What the two flags do
Reading the loader, the logic is:
1. If the <script> tag that loads office.js has a data-enable-trusted-types attribute, that attribute decides.
2. Otherwise, isOsfSecurityDefaultsEnabled decides.
3. If the result is "on", the loader does not load MicrosoftAjax and creates a Trusted Types policy named officejs.
On the legacy endpoint neither flag is set, so neither happens unless you opt in. On the new endpoint both happen by default.
Another detail: the loader decides where to fetch host files from based on its own src. An add-in that loads office.js from the new endpoint gets all of its Office.js files from there, not a mix.
Who can break when switching
Add-ins that use MicrosoftAjax without knowing it. The legacy loader quietly pulls in MicrosoftAjax.js, so its globals have always been there. Code (yours or a dependency's) that touches Sys.*, Type.registerNamespace, Function.createDelegate or $create works today and throws ReferenceError on the new endpoint. Search your bundle, not just your source:
grep -rnE "\bSys\.|Type\.registerNamespace|Function\.createDelegate|\$create\(" dist/
Pages with a strict CSP or Trusted Types. With require-trusted-types-for 'script', the officejs policy has to be allowed (trusted-types officejs …), and *.static.microsoft has to be allowed in script-src and in any corporate network allow list.
Microsoft's migration checklist also asks you to register every ExecuteFunction command handler with Office.actions.associate(...). The loader diff contains nothing beyond the two flags, so that requirement comes from code paths that behave differently once the flags are on, not from a separate switch.
Trying the new defaults without changing the URL
Because the decision starts with the data-enable-trusted-types attribute, you should be able to get the new endpoint's behaviour on the legacy URL:
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"
data-enable-trusted-types="true"></script>
We read this from the loader and haven't tested it in every host. Treat it as a quick way to find MicrosoftAjax dependencies in a test build, not as a production setting.
The risk worth watching
Today the only difference between the endpoints is two flags in one file. Nothing stops the same two lines from appearing in the legacy loader one day. Microsoft ships loader updates the way it ships everything else on this CDN: without a changelog, edge by edge. If that happens, add-ins that still depend on MicrosoftAjax will start failing on a growing share of loads over a day or so, without a single line of their own code changing.
silentship now watches both endpoints. A change to either flag on any endpoint is treated as High, and subscribers are alerted as soon as it's confirmed on two runs. You can see both endpoints side by side on every [rollout page](/rollouts/prod/16.0.20606.15070).
Alerts: silentship.dev
---
Checked on 19 Oct 2026 against build 16.0.20606.15070. Microsoft's page: Referencing the Office JavaScript API library from its CDN. Thanks to the commenter on office-js#6513 who pointed out the new endpoint.
1 reaction
A 33-hour Office.js rollout that changed four bytes
If you build Office add-ins, you load Office.js from one fixed URL:
https://appsforoffice.microsoft.com/lib/1/hosted/office.js
There is no version in that URL, no changelog for what it serves, and no Subresource Integrity hash you could pin. Microsoft ships new code behind it whenever it decides to, and your add-in picks it up the next time Office loads it. In February, 233 developers discussed exactly this in an open letter on the office-js repo: add-ins breaking with no warning, no record of what changed, and no way to tell whether a bug is theirs or Microsoft's.
I wanted to see what one of those silent updates actually looks like from the outside. So I built a small monitor, silentship, and caught a full rollout from (almost) start to finish.
How the monitor works
- The loader tells you which files exist. office.js is only a bootstrapper. It contains a map of every host and platform (excel-win32-16.01.js, outlook-web-16.01.js, …), so the crawler builds its file list from the loader itself.
- Every file carries its own build stamp. There isn't one "Office.js version". PowerPoint and OneNote files have a // host: 16.0\16.0.x header, Excel and Word embed hostJSVersion, and Outlook lives on a separate build line entirely (osfweb version: 16.0.20410.x when I looked).
- Sampling, not polling. Every 10 minutes each file is requested 8 times, each on a fresh TLS connection, so the CDN spreads the requests across different edge servers. Requests are conditional on the stable version's Last-Modified: edges still serving the old file answer 304 with no body, so only a new version costs bandwidth. The share of samples that return the new build is the "rollout %".
- API diff by reflection. New versions are executed in a locked-down sandbox (separate process, empty environment, Node permission model) and the public API surface (Office.*, Excel.*, Word.*, …) is walked and diffed against the previous build. API status (GA, preview, undocumented) comes from Microsoft's own @types/office-js and @types/office-js-preview.
- No Microsoft code is published. The site shows metadata and API-surface diffs only.
The rollout: 16.0.20528 → 16.0.20606
!Share of sampled CDN edges serving 16.0.20606
| | Production | Beta |
|---|---|---|
| First seen | Oct 14, 20:06 UTC | Oct 14, 20:07 UTC |
| Complete (100% for 3 runs) | Oct 16, 04:50 UTC | Oct 16, 04:50 UTC |
| Duration (observed) | ~33 h | ~33 h |
| Host files | 22 | 20 |
| Public API changes | 0 (only the build stamp changed) | 44, in 12 types |
A few things surprised me.
It was already moving when I first looked. On the very first run some files were well past the start line: beta excel-web was on 87.5% of samples, production excel-winrt on 75%. So "20:06" is an upper bound for the start, not the start.
Hosts don't move together. In production, visio-web and excel-winrt were complete within the first hour. word-win32 was the very last file in both channels, about 32 hours later.
There's a long plateau. For roughly 20 hours the overall share sat between 40% and 60% and went up and down: the same edge would serve the new build on one run and the old one on the next. Then, between midnight and 02:00 UTC on Oct 16, it jumped from ~55% to ~95% and finished a few hours later.
In practice, that plateau is the part that hurts. For most of a day, two users of the same add-in, on the same host, could load different Office.js builds, depending only on which edge answered. If you ever had a bug report that you couldn't reproduce and that "fixed itself" the next day, this is one way that happens.
What actually changed: four bytes (in production)
The API diff for production came back empty, so I compared the files themselves. For 21 of the 22 production files, 20606 is byte-for-byte identical to 20528 except for the embedded build stamp. In excel-win32-16.01.js, the whole 33-hour rollout changed exactly four bytes: hostJSVersion="16.0.20528…" became "16.0.20606…".
So in production this was a version bump, not a code change. Nothing about it could break your add-in. But for a day and a half your users were still loading two "different" builds of the same code, and from the outside you can't tell which kind of rollout you're in until you diff it. The next one may not be a no-op.
What changed in the API (beta)
Beta is where the real changes went: 44 changes across Excel and PowerPoint files, grouping into 12 types. Almost all of it is preview API, which Microsoft serves only from the beta CDN:
- Removed (preview): Excel.SensitivityLabelAbacAttribute, Excel.SensitivityLabelAbacAttributeValue, and their PowerPoint counterparts.
- Renamed (preview): SensitivityLabelUpdateResult → SensitivityLabelUpdateStatus in both Excel and PowerPoint.
- Added (not in Microsoft's type definitions yet): Excel.AllowedUrls, Excel.Copilot.allowedUrls, a pivotTableEdit value on Excel.UserActivityType.
- Enum value added: tabs on Excel.EntityCardLayoutType, which Microsoft's types list as GA (ExcelApi 1.19). It isn't on the production CDN yet: production doesn't even have the array value that beta already shipped.
None of this should break a production add-in; preview APIs come with an explicit "do not use in production" warning. But if you were experimenting with sensitivity labels on the beta CDN, your code started throwing TypeError: Excel.SensitivityLabelAbacAttribute is not a constructor sometime on Oct 14–15, gradually, on a growing share of loads. Each changed type has its own page with the exact error messages, e.g. Excel.SensitivityLabelAbacAttribute.
What this can and can't see
- Only the JavaScript layer. Plenty of add-in breakage comes from the native hosts (Outlook classic vs. new Outlook, Excel on Windows vs. web), and that never touches the CDN. If your add-in broke and nothing shows up here, that's useful information too: it's probably not the CDN.
- One vantage point. All samples come from a European server. Other regions may see the same rollout at a different pace.
- 8 samples per file means a single run moves in 12.5% steps; the site smooths over the last hour.
Try it
- Live status and every rollout: silentship.dev
- RSS feed of new builds
- Email alerts are optional, and only for builds that change the public API
I'd love to hear from people who maintain add-ins in production: did this rollout line up with anything you saw? And what would make this actually useful to you: per-API watchlists, a webhook into your CI, something else?
4 reactions
Mentions
What the new Office.js CDN endpoint actually changes I run silentship, an independent monitor for changes on the Office.js CDN. This is a copy of the original note. Since late September, Microsoft's docs list appsforoffice.microsoft.com as a legacy endpoint and recommend a new one: | | Legacy | New | |---|---|---| | Release | appsforoffice.microsoft.com/lib/1/hosted/office.js | officeapis.public.onecdn.static.microsoft/1/office.js | | Preview | appsforoffice.microsoft.com/lib/beta/hosted/office.js | officeapis.public.onecdn.static.microsoft/beta/office.js | The docs say the new endpoint "serves the same current Office.js release, but it enables additional secure defaults", without saying which defaults. So we compared the two directly. Short answer - Same build. Both endpoints serve 16.0.20606.15070. - Host files are byte-identical. excel-win32-16.01.js, word-win32-16.01.js, outlook-win32-16.02.js, powerpoint-web-16.00.js and the rest have the same SHA-256 on both. Every Office API your add-in calls comes from the same code. - Only office.js (the loader) differs, and only by two lines at the top: isOsfSecurityDefaultsEnabled = true; osfLoadMsAjax = false; Everything else in the loader is identical. Both endpoints also sit behind the same Akamai + Azure Front Door setup, with the same caching headers. What the two flags do Reading the loader, the logic is: 1. If the <script> tag that loads office.js has a data-enable-trusted-types attribute, that attribute decides. 2. Otherwise, isOsfSecurityDefaultsEnabled decides. 3. If the result is "on", the loader does not load MicrosoftAjax and creates a Trusted Types policy named officejs. On the legacy endpoint neither flag is set, so neither happens unless you opt in. On the new endpoint both happen by default. Another detail: the loader decides where to fetch host files from based on its own src. An add-in that loads office.js from the new endpoint gets all of its Office.js files from there, not a mix. Who can break when switching Add-ins that use MicrosoftAjax without knowing it. The legacy loader quietly pulls in MicrosoftAjax.js, so its globals have always been there. Code (yours or a dependency's) that touches Sys.*, Type.registerNamespace, Function.createDelegate or $create works today and throws ReferenceError on the new endpoint. Search your bundle, not just your source: grep -rnE "\bSys\.|Type\.registerNamespace|Function\.createDelegate|\$create\(" dist/ Pages with a strict CSP or Trusted Types. With require-trusted-types-for 'script', the officejs policy has to be allowed (trusted-types officejs …), and *.static.microsoft has to be allowed in script-src and in any corporate network allow list. Microsoft's migration checklist also asks you to register every ExecuteFunction command handler with Office.actions.associate(...). The loader diff contains nothing beyond the two flags, so that requirement comes from code paths that behave differently once the flags are on, not from a separate switch. Trying the new defaults without changing the URL Because the decision starts with the data-enable-trusted-types attribute, you should be able to get the new endpoint's behaviour on the legacy URL: <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" data-enable-trusted-types="true"></script> We read this from the loader and haven't tested it in every host. Treat it as a quick way to find MicrosoftAjax dependencies in a test build, not as a production setting. The risk worth watching Today the only difference between the endpoints is two flags in one file. Nothing stops the same two lines from appearing in the legacy loader one day. Microsoft ships loader updates the way it ships everything else on this CDN: without a changelog, edge by edge. If that happens, add-ins that still depend on MicrosoftAjax will start failing on a growing share of loads over a day or so, without a single line of their own code changing. silentship now watches both endpoints. A change to either flag on any endpoint is treated as High, and subscribers are alerted as soon as it's confirmed on two runs. You can see both endpoints side by side on every [rollout page](/rollouts/prod/16.0.20606.15070). Alerts: silentship.dev --- Checked on 19 Oct 2026 against build 16.0.20606.15070. Microsoft's page: Referencing the Office JavaScript API library from its CDN. Thanks to the commenter on office-js#6513 who pointed out the new endpoint.
A 33-hour Office.js rollout that changed four bytes If you build Office add-ins, you load Office.js from one fixed URL: https://appsforoffice.microsoft.com/lib/1/hosted/office.js There is no version in that URL, no changelog for what it serves, and no Subresource Integrity hash you could pin. Microsoft ships new code behind it whenever it decides to, and your add-in picks it up the next time Office loads it. In February, 233 developers discussed exactly this in an open letter on the office-js repo: add-ins breaking with no warning, no record of what changed, and no way to tell whether a bug is theirs or Microsoft's. I wanted to see what one of those silent updates actually looks like from the outside. So I built a small monitor, silentship, and caught a full rollout from (almost) start to finish. How the monitor works - The loader tells you which files exist. office.js is only a bootstrapper. It contains a map of every host and platform (excel-win32-16.01.js, outlook-web-16.01.js, …), so the crawler builds its file list from the loader itself. - Every file carries its own build stamp. There isn't one "Office.js version". PowerPoint and OneNote files have a // host: 16.0\16.0.x header, Excel and Word embed hostJSVersion, and Outlook lives on a separate build line entirely (osfweb version: 16.0.20410.x when I looked). - Sampling, not polling. Every 10 minutes each file is requested 8 times, each on a fresh TLS connection, so the CDN spreads the requests across different edge servers. Requests are conditional on the stable version's Last-Modified: edges still serving the old file answer 304 with no body, so only a new version costs bandwidth. The share of samples that return the new build is the "rollout %". - API diff by reflection. New versions are executed in a locked-down sandbox (separate process, empty environment, Node permission model) and the public API surface (Office.*, Excel.*, Word.*, …) is walked and diffed against the previous build. API status (GA, preview, undocumented) comes from Microsoft's own @types/office-js and @types/office-js-preview. - No Microsoft code is published. The site shows metadata and API-surface diffs only. The rollout: 16.0.20528 → 16.0.20606 !Share of sampled CDN edges serving 16.0.20606 | | Production | Beta | |---|---|---| | First seen | Oct 14, 20:06 UTC | Oct 14, 20:07 UTC | | Complete (100% for 3 runs) | Oct 16, 04:50 UTC | Oct 16, 04:50 UTC | | Duration (observed) | ~33 h | ~33 h | | Host files | 22 | 20 | | Public API changes | 0 (only the build stamp changed) | 44, in 12 types | A few things surprised me. It was already moving when I first looked. On the very first run some files were well past the start line: beta excel-web was on 87.5% of samples, production excel-winrt on 75%. So "20:06" is an upper bound for the start, not the start. Hosts don't move together. In production, visio-web and excel-winrt were complete within the first hour. word-win32 was the very last file in both channels, about 32 hours later. There's a long plateau. For roughly 20 hours the overall share sat between 40% and 60% and went up and down: the same edge would serve the new build on one run and the old one on the next. Then, between midnight and 02:00 UTC on Oct 16, it jumped from ~55% to ~95% and finished a few hours later. In practice, that plateau is the part that hurts. For most of a day, two users of the same add-in, on the same host, could load different Office.js builds, depending only on which edge answered. If you ever had a bug report that you couldn't reproduce and that "fixed itself" the next day, this is one way that happens. What actually changed: four bytes (in production) The API diff for production came back empty, so I compared the files themselves. For 21 of the 22 production files, 20606 is byte-for-byte identical to 20528 except for the embedded build stamp. In excel-win32-16.01.js, the whole 33-hour rollout changed exactly four bytes: hostJSVersion="16.0.20528…" became "16.0.20606…". So in production this was a version bump, not a code change. Nothing about it could break your add-in. But for a day and a half your users were still loading two "different" builds of the same code, and from the outside you can't tell which kind of rollout you're in until you diff it. The next one may not be a no-op. What changed in the API (beta) Beta is where the real changes went: 44 changes across Excel and PowerPoint files, grouping into 12 types. Almost all of it is preview API, which Microsoft serves only from the beta CDN: - Removed (preview): Excel.SensitivityLabelAbacAttribute, Excel.SensitivityLabelAbacAttributeValue, and their PowerPoint counterparts. - Renamed (preview): SensitivityLabelUpdateResult → SensitivityLabelUpdateStatus in both Excel and PowerPoint. - Added (not in Microsoft's type definitions yet): Excel.AllowedUrls, Excel.Copilot.allowedUrls, a pivotTableEdit value on Excel.UserActivityType. - Enum value added: tabs on Excel.EntityCardLayoutType, which Microsoft's types list as GA (ExcelApi 1.19). It isn't on the production CDN yet: production doesn't even have the array value that beta already shipped. None of this should break a production add-in; preview APIs come with an explicit "do not use in production" warning. But if you were experimenting with sensitivity labels on the beta CDN, your code started throwing TypeError: Excel.SensitivityLabelAbacAttribute is not a constructor sometime on Oct 14–15, gradually, on a growing share of loads. Each changed type has its own page with the exact error messages, e.g. Excel.SensitivityLabelAbacAttribute. What this can and can't see - Only the JavaScript layer. Plenty of add-in breakage comes from the native hosts (Outlook classic vs. new Outlook, Excel on Windows vs. web), and that never touches the CDN. If your add-in broke and nothing shows up here, that's useful information too: it's probably not the CDN. - One vantage point. All samples come from a European server. Other regions may see the same rollout at a different pace. - 8 samples per file means a single run moves in 12.5% steps; the site smooths over the last hour. Try it - Live status and every rollout: silentship.dev - RSS feed of new builds - Email alerts are optional, and only for builds that change the public API I'd love to hear from people who maintain add-ins in production: did this rollout line up with anything you saw? And what would make this actually useful to you: per-API watchlists, a webhook into your CI, something else?