# CORS issues for DatoCMS Plugins

## Introduction: What is CORS?

> [!PROTIP] Pro tip: Recommended pre-reading
> If you're not already familiar with Cross-Origin Resource Sharing (CORS), we recommend reading the following explainers first:
> 
> -   [**MDN: Cross-Origin Resource Sharing**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for a basic technical background
>     
> -   ["How to win at CORS" by Jake Archibald](https://jakearchibald.com/2021/cors/) for a longer history of CORS and why it exists

In brief, CORS is a browser-side security mechanism that lets a domain declare which *other* domains are allowed to make clientside API calls to itself. For example, `my-cors-protected-domain.com` can tell browsers that only `official-partner-site.com` can make clientside API calls to it, while rejecting all other attempts.

In context for you as a plugin author, this means:

-   Your plugin would normally be hosted on `plugins-cdn.datocms.com` (if you [published it to the DatoCMS Marketplace](https://www.datocms.com/docs/plugin-sdk/publishing-to-marketplace.md))
-   If you try to make a clientside browser `fetch()` to `another-domain.com`, the other domain gets to decide whether this is allowed. If they say no (or don't return a CORS header at all), the browser will fail your request with a CORS error
    

## As a plugin author, when do I need to worry about CORS?

In the context of plugin authoring, **CORS is only an issue when all the following criteria are met:**

-   Your plugin **lives on one domain** (like our default `plugins-cdn.datocms.com`) but needs to **make an API call to another domain**, like a third-party translation provider, an LLM API, etc.
-   The other **API endpoint does not natively support CORS**, or does not allow our originating domain (`plugins-cdn.datocms.com`) in its CORS setup.
    
-   The API call is made via a **clientside JavaScript** **`fetch()`** **or** **`XMLHttpRequest`**
    

Only when ALL of these conditions are true will you need to work around CORS.

### When CORS is NOT an issue

You do NOT have to worry about CORS if your plugin:

-   Is only making calls to DatoCMS endpoints (like the [Content Management API](https://www.datocms.com/docs/content-management-api.md) endpoints). We handle CORS for you when a plugin makes calls to other Dato-owned endpoints.
-   Is proxying its requests *serverside* (such as via a Next API route, serverless/lambda function, or your own backend). CORS only affects cross-origin **browser** (clientside) requests, not server-to-server.
    
-   Is making requests to the same *origin* (scheme + domain + port) it's hosted on. This is most common for private, internal-use plugins, such as one hosted on `www.your-domain.com/plugin` and making API calls to `www.your-domain.com/api`. Note that different *subdomains* (like `plugin.your-domain.com`) DO count as cross-origin traffic, and ARE subject to CORS rules. Different ports have the same problem (`your-domain.com:443` is a different origin than `your-domain.com:1234`). In those cases, see below for solutions.
    

## How do I fix CORS issues with my plugin?

### Scenario 1: Add CORS headers to an API endpoint under your control

If you control the destination endpoint (i.e., it's your own backend API you want to fetch from), then the proper solution is to add the correct CORS headers to that endpoint so that it will allow `fetch()` from `plugins-cdn.datocms.com`.

If you're not sure how to add the correct CORS headers, these resources may help (or just ask your favorite LLM):

-   "[Will it CORS?](https://httptoolkit.com/will-it-cors/)", an interactive wizard that walks you through CORS troubleshooting step-by-step
-   [How to enable CORS on Vercel](https://vercel.com/kb/guide/how-to-enable-cors)
    
-   [Handling CORS on Netlify](https://answers.netlify.com/t/support-guide-handling-cors-on-netlify/107739)
-   [Setting CORS headers with Astro on Netlify](https://dev.to/cassidoo/three-ways-to-set-headers-with-netlify-and-astro-1iib)
    
-   [AWS Lambda Function URL with CORS explained by example](https://dev.to/rimutaka/aws-lambda-function-url-with-cors-explained-by-example-14df)
    

### **Scenario 2: Use a serverside CORS proxy for third-party endpoints**

(We also provide an official proxy for DatoCMS plugins! See below.)

If you do not control the destination endpoint (it's a third-party API), you will need to create a workaround, usually via a serverside proxy that injects the proper CORS headers into the third-party API's response before returning it to the browser.

You can either make your own (see [Cloudflare Workers CORS proxy example](https://developers.cloudflare.com/workers/examples/cors-header-proxy/)), or use the proxy DatoCMS provides specifically for this use, below.

Your own proxy doesn't need be on Cloudflare or serverless or any particular server. CORS is enforced only by web browsers, so any server-side environment (Node.js, a serverless function, your existing backend) can make the request on your plugin's behalf. The proxy just has to do two things: forward the request to the target API, and return the response with its own CORS headers injected. The Cloudflare Workers example is just to show you the basic concept. Any modern LLM can write a CORS proxy for you in the environment of your choice.

## DatoCMS CORS Proxy for Plugins

For plugin authors running into CORS issues, we provide an official proxy to help bypass them. This is similar in spirit to the Cloudflare Workers proxy example, above, but tweaked specifically for the needs of DatoCMS plugins.

### How to use

Usage is simple: Replace your existing `fetch()` calls to the third-party endpoint with our proxy URL, `cors-proxy.datocms.com`, and pass it the original target URL as the `url` search param, URL-encoded.

It is just an HTTP proxy. There's nothing for you to download or install, just a simple rewrite in your code.

### **Example**

```javascript
// The API you're actually trying to reach
const targetUrl = 'https://www.third-party.com/api';

// But instead of calling it directly, route it through our proxy
// The `url` param must be URL-encoded, since the target URL contains
// characters (:, /, ?) that would otherwise break the proxy URL.
const response = await fetch(
  `https://cors-proxy.datocms.com/?url=${encodeURIComponent(targetUrl)}`,
);

// Continue processing the response, e.g.
// const data = await response.json()
// doSomethingWith(data);
```

This is a simple proxy that sits between your plugin and the destination API endpoint. Its sole job is to inject the necessary CORS headers into the third-party API response before returning it to the browser, thereby fooling the browser and bypassing the CORS errors.

### Limitations

Our official proxy has some limitations you should be aware of:

-   Only Marketplace-hosted plugins on `plugins-cdn.datocms.com` and `localhost` (any port) are allowed. If your plugin is hosted internally or on another domain, our proxy will not work.
-   **Supported request headers** (that you send)**:** Only `Content-Type` and `Authorization`. Other headers will cause an error.
    
-   **Supported HTTP methods:** Only `GET`, `POST`, `PUT`, `HEAD`, `DELETE`, and `OPTIONS`. `PATCH` and other methods are not supported and will cause an error.
-   **No SLA, performance, or uptime guarantee:** The proxy is provided as-is, on a best-effort basis. We make no guarantee as to its availability or performance at any time, regardless of your plan or other agreements with us. It is not considered a part of your regular service level agreement (SLA), if you have one. That said, it's usually stable and rarely has issues.
    

If you need functionality not supported by our official proxy, you should write your own (see the Cloudflare Workers example in Scenario 2, above).

### Privacy

When you use the CORS proxy, your request will go through DatoCMS servers and our upstream providers. We may log some information for debugging purposes (such as the target URL); such logs are only used for troubleshooting and to fix issues with the CORS proxy. However, we don't take any special effort to scrub any information that may be in these logs.

We recommend only using our proxy for non-sensitive data — any trade secrets, personally identifiable information, or other sensitive information should not go through this proxy.

The destination API will see the request as coming from our IP address instead of yours.

## Related content in "Plugin SDK"

- [Introduction to the DatoCMS Plugin SDK](https://www.datocms.com/docs/plugin-sdk/introduction.md)
- [Build your first DatoCMS plugin](https://www.datocms.com/docs/plugin-sdk/build-your-first-plugin.md)
- [Real-world examples](https://www.datocms.com/docs/plugin-sdk/real-world-examples.md)
- [What hooks are](https://www.datocms.com/docs/plugin-sdk/what-hooks-are.md)
- [Config screen](https://www.datocms.com/docs/plugin-sdk/config-screen.md)
- [Custom pages](https://www.datocms.com/docs/plugin-sdk/custom-pages.md)
- [Sidebars and sidebar panels](https://www.datocms.com/docs/plugin-sdk/sidebar-panels.md)
- [Outlets](https://www.datocms.com/docs/plugin-sdk/form-outlets.md)
- [Field extensions](https://www.datocms.com/docs/plugin-sdk/field-extensions.md)
- [Manual field extensions](https://www.datocms.com/docs/plugin-sdk/manual-field-extensions.md)
- [Dropdown actions](https://www.datocms.com/docs/plugin-sdk/dropdown-actions.md)
- [Structured Text customizations](https://www.datocms.com/docs/plugin-sdk/structured-text-customizations.md)
- [Asset sources](https://www.datocms.com/docs/plugin-sdk/asset-sources.md)
- [Opening modals](https://www.datocms.com/docs/plugin-sdk/modals.md)
- [Event hooks](https://www.datocms.com/docs/plugin-sdk/event-hooks.md)
- [Customize record presentation](https://www.datocms.com/docs/plugin-sdk/customize-presentation.md)
- [React UI Components](https://www.datocms.com/docs/plugin-sdk/react-datocms-ui.md)
- [Button](https://www.datocms.com/docs/plugin-sdk/button.md)
- [Button group](https://www.datocms.com/docs/plugin-sdk/button-group.md)
- [Dropdown](https://www.datocms.com/docs/plugin-sdk/dropdown.md)
- [Form](https://www.datocms.com/docs/plugin-sdk/form.md)
- [Section](https://www.datocms.com/docs/plugin-sdk/section.md)
- [Sidebar panel](https://www.datocms.com/docs/plugin-sdk/sidebar-panel.md)
- [Spinner](https://www.datocms.com/docs/plugin-sdk/spinner.md)
- [Toolbar](https://www.datocms.com/docs/plugin-sdk/toolbar.md)
- [Sidebars and split views](https://www.datocms.com/docs/plugin-sdk/sidebars-and-split-views.md)
- [Additional permissions](https://www.datocms.com/docs/plugin-sdk/additional-permissions.md)
- [Working with form values](https://www.datocms.com/docs/plugin-sdk/working-with-form-values.md)
- [CORS issues for DatoCMS Plugins](https://www.datocms.com/docs/plugin-sdk/cors-issues-for-datocms-plugins.md)
- [Publishing to Marketplace](https://www.datocms.com/docs/plugin-sdk/publishing-to-marketplace.md)
- [Releasing new plugin versions](https://www.datocms.com/docs/plugin-sdk/releasing-new-plugin-versions.md)
- [Migrating from legacy plugins](https://www.datocms.com/docs/plugin-sdk/migrating-from-legacy-plugins.md)
- [Upgrading plugins for dark mode](https://www.datocms.com/docs/plugin-sdk/upgrading-plugins-for-dark-mode.md)