GuidesOFFICE ADD-INS
Office add-in CORS errors
An Office add-in task pane is a web page in a webview, so fetch() carries the origin of the page you host (https://localhost:3000 in the default dev setup) and fails against APIs that send no Access-Control-Allow-Origin. Microsoft points to CORS on the API or a server-side proxy, and Office Scripts need APIs that answer Access-Control-Allow-Origin: *, because their runtime origin can change. https://proxy.cors.dev/ covers public GET requests in both, since it echoes whichever origin calls it (free, about 600 requests a minute per IP); POST and Authorization from a task pane need cors.dev Pro at $5 a month for 500,000 requests.
Why an add-in is subject to CORS
A request that works in Postman can fail in a task pane, because Postman is not a browser and Office add-ins run in one. Microsoft's "Addressing same-origin policy limitations in Office Add-ins": "Because Office Add-ins are hosted in a webview control, the same-origin policy applies to script running in their web pages as well." When the API on another domain sends no Access-Control-Allow-Origin, the webview blocks your JavaScript from reading the response.
Microsoft describes four techniques:
- Token-based authorization: Server-side code that uses OAuth 2.0 flows to reach resources on another domain.
- CORS: The API sends
Access-Control-Allow-Originfor your add-in's origin. - iframe and postMessage: Cross-window messaging with a page loaded in an iframe from another domain.
- JSONP: A legacy technique, in Microsoft's words, with "higher security risk than modern alternatives". For new development it says to "prefer CORS or a server-side proxy."
The origin your add-in sends
The task pane is the web page at the SourceLocation URL in your add-in manifest, and it must use HTTPS. Office on the web loads it in an iframe that runs with the HTML5 sandbox attribute, Microsoft 365 on Windows uses WebView2 (Microsoft Edge, Chromium), and Office on Mac and iPad uses WKWebView (Safari). In every client, requests carry the origin of the server that hosts that page.
Add-in projects created with the Yeoman generator for Office Add-ins serve the task pane from https://localhost:3000 with a development certificate from office-addin-dev-certs. During development, every API you call sees Origin: https://localhost:3000.
| Client | Runtime | Origin an API sees |
|---|---|---|
| Office on the web | iframe with the HTML5 sandbox attribute | Your SourceLocation origin |
| Microsoft 365 on Windows | WebView2, Microsoft Edge | Your SourceLocation origin |
| Office on Mac and iPad | WKWebView, Safari | Your SourceLocation origin |
| Local development | The client's runtime | https://localhost:3000 |
| Office Scripts | Excel's script runtime | Not documented, may change |
AppDomains in the manifest lists domains the add-in may navigate to inside its window. It adds no CORS headers to anyone's API and does not fix a CORS error.
Load API data into an Excel range
Check the API first. On 28 October 2026 we called four endpoints with Origin: https://localhost:3000. Microsoft Graph answered its OPTIONS preflight with 200 and Access-Control-Allow-Origin: *, and a GET without a token answered 401 with the same header, so call Graph directly. The GitHub REST API also sends *.
The ECB daily euro rates XML (https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml, 1,547 bytes) and Yahoo Finance's chart API (https://query1.finance.yahoo.com/v8/finance/chart/MSFT?range=1mo&interval=1d, 3,553 bytes) sent no Access-Control-Allow-Origin. Through https://proxy.cors.dev/ both answered 200 with the header echoing https://localhost:3000. The free tier needs no signup and no key.
| API | Access-Control-Allow-Origin | What to do |
|---|---|---|
| Microsoft Graph | * | Call it directly |
| GitHub REST API | * | Call it directly |
| ECB euro rates XML | None | Use the proxy, 200 |
| Yahoo Finance chart | None | Use the proxy, 200 |
- Fetch through the proxy
Put
https://proxy.cors.dev/in front of the ECB URL and parse thetext/xmlresponse withDOMParser. - Build rows
Microsoft's docs say to set
range.valueswith a two-dimensional array, even for a single cell, where each inner array is one row. On 28 October 2026 the file held 29 currencies, so a header plus 29 rates fillsA1:B30(USD 1.1133, JPY 175.86 that day). - Write the range
Assign the array inside
Excel.runand callcontext.sync().
const FEED = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
export async function loadEuroRates() {
const response = await fetch('https://proxy.cors.dev/' + FEED);
if (!response.ok) {
throw new Error(response.headers.get('X-Cors-Error') ?? `HTTP ${response.status}`);
}
const xml = new DOMParser().parseFromString(await response.text(), 'text/xml');
const rows = [...xml.querySelectorAll('Cube[currency]')].map((cube) => [
cube.getAttribute('currency'),
Number(cube.getAttribute('rate')),
]);
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
sheet.getRange(`A1:B${rows.length + 1}`).values = [['Currency', 'Per 1 EUR'], ...rows];
await context.sync();
});
}The free tier sends GET and HEAD only and allows about 600 requests per minute per IP, with a readable Retry-After on 429. It refuses Authorization, and header names containing token, apikey, api-key, api_key, secret or password or ending in -key, with 400 header_not_allowed; names containing csrf or xsrf, such as X-CSRF-Token, are dropped and listed in X-Cors-Dropped-Headers like other custom headers. When the proxy refuses a request, the X-Cors-Error response header names the reason.
Office Scripts and fetch
Office Scripts, Excel's TypeScript automation, can call fetch() with limits from Microsoft's "External API call support in Office Scripts". Calls work only when the script runs in the Excel application, not through Power Automate "under normal circumstances", and not for scripts stored on a SharePoint site. Microsoft adds: "There is no way to sign in or use OAuth2 type of authentication flows." and "There is no infrastructure to store API credentials and keys."
The CORS rule is stricter than for add-ins: "make sure that the Access-Control-Allow-Origin header of that external resource uses the * directive. If the Access-Control-Allow-Origin header uses a specific <origin> directive, then your fetch call from Office Scripts may fail. The origin of the Office Scripts runtime may change without notice."
The free tier echoes whatever origin a request carries and answers * when there is none, so a script that calls an API through https://proxy.cors.dev/ keeps matching when the runtime origin changes. On 28 October 2026 the Yahoo Finance request below returned 22 daily MSFT closes, written as 22 rows of date and price.
interface ChartResponse {
chart: { result: { timestamp: number[]; indicators: { quote: { close: number[] }[] } }[] };
}
async function main(workbook: ExcelScript.Workbook): Promise<void> {
const target = 'https://query1.finance.yahoo.com/v8/finance/chart/MSFT?range=1mo&interval=1d';
const response = await fetch('https://proxy.cors.dev/' + target);
if (!response.ok) {
throw new Error(`Proxy answered HTTP ${response.status}`);
}
const data: ChartResponse = await response.json();
const { timestamp, indicators } = data.chart.result[0];
const rows = timestamp.map((seconds, i) => [
new Date(seconds * 1000).toISOString().slice(0, 10),
indicators.quote[0].close[i],
]);
workbook.getActiveWorksheet().getRange(`A1:B${rows.length}`).setValues(rows);
}When you need cors.dev Pro
A task pane that sends POST, PUT, PATCH or DELETE, a body up to 1 MiB, or Authorization and custom headers needs cors.dev Pro. It fits add-ins where each user enters their own API key in the task pane. A key your company owns belongs on your server: cors.dev never hides a key, and anything in the task pane is visible to the user.
cors.dev Pro costs $5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card. Throughput is 3,000 requests a minute per account and 100 in flight; the 6 MiB response cap and 30 second upstream deadline stay the same. An X-Cors-Cache header caches a GET for up to 300 seconds when the upstream response allows it; cache hits still count as requests.
- Create a Connection
Sign up with Google and add up to 10 exact page origins and up to 20 target hosts.
- Register both origins
The origin of your production
SourceLocation, for examplehttps://addin.contoso.com, andhttps://localhost:3000. - Send the key
Add the publishable key in an
X-Cors-Keyheader. An unlisted origin answers403origin_not_allowed.
Pro needs every exact origin registered, so it is a poor fit for Office Scripts, whose origin can change without notice.
Good questions.
Does adding a domain to AppDomains fix a CORS error?
No. AppDomains tells Office which domains the add-in may navigate to inside its window. It adds no Access-Control-Allow-Origin header to any API response, so fetch() still fails until the API sends CORS headers or the request goes through a proxy.
Can an Office Script call an API when it runs from Power Automate?
No. Microsoft says external calls work only when the script runs in the Excel application, and not for scripts stored on a SharePoint site. Run through Power Automate, a script with fetch() fails with "Runtime error: Line X: fetch is not defined".
Should I use JSONP in an Office add-in?
Only to maintain an old integration. Microsoft calls JSONP a legacy technique with higher security risk than modern alternatives and says new development should prefer CORS or a server-side proxy.
Which origins do I register for an add-in on cors.dev Pro?
The exact origin of the SourceLocation URL in your production manifest, for example https://addin.contoso.com, and https://localhost:3000 for local development. Requests from any other origin answer 403 origin_not_allowed.
Does Microsoft Graph need a CORS proxy?
No. On 28 October 2026 Graph answered an OPTIONS preflight from https://localhost:3000 with 200 and Access-Control-Allow-Origin: *, and sent the same header on a 401 response to a GET without a token. Call it directly with your access token.