Cross-Origin Resource Sharing

How to troubleshoot and fix CORS errors.

While developing an app, you may encounter errors when attempting to send HTTP requests. This includes requests sent with the Fetch API or requests to load third-party assets, such as thumbnail images.

A common reason for these errors is Cross-Origin Resource Sharing (CORS).

Cross-Origin Resource Sharing (CORS) is a security feature of web browsers that blocks client-side HTTP requests between different origins.

An origin is the unique combination of a domain, protocol, and port. This means that, although the following URLs all point to the same domain, they are all different origins:

  • http://abc.com
  • https://abc.com
  • https://abc.com:3000
  • https://abc.com:9090

This is important because the origin of the app's frontend and backend are inevitably different. For example, the app's frontend is hosted on Canva's servers and its origin will look something like this:

https://app-CANVA_APP_ID.canva-apps.com

The backend, on the other hand, must be hosted on your own infrastructure and will have its own origin:

https://www.example.com

As a result, any HTTP requests that your app's frontend sends to your app's backend will fail unless your explicitly configure the backend to support CORS. This is an unavoidable feature of all modern web browsers.

To confirm that an error is occurring as a result of CORS:

  1. Preview the app in the Canva editor.
  2. Open the JavaScript Console via the browser's developer tools.
  3. Trigger an HTTP request from the app's frontend.

If CORS is the source of the problem, the following error will appear in the console:

No 'Access-Control-Allow-Origin' header is present on the requested resource.

Alternatively, use test-cors.org to check the CORS headers on a particular endpoint.

An app's frontend is served via its own subdomain:

https://app-CANVA_APP_ID.canva-apps.com

To fix CORS errors, the backend must set an Access-Control-Allow-Origin HTTP header that explicitly allows requests from this origin. This will prevent the browser from blocking the requests.

How the backend sets the header depends on the language and framework, and it's not practical to explain every possible option, but many modern frameworks provide mechanisms for streamlining this process.

The following code sample demonstrates how to handle CORS with Express.js:

import express from "express";
import cors from "cors";
const CANVA_APP_ID = process.env.CANVA_APP_ID?.toLowerCase(); // The ID must be lowercase
if (!CANVA_APP_ID) {
throw new Error(`CANVA_APP_ID environment variable is not set`);
}
const app = express();
app.use(
cors({
origin: `https://app-${CANVA_APP_ID}.canva-apps.com`,
optionsSuccessStatus: 200,
})
);
app.get("/", (req, res) => {
res.send("Hello world");
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
ts

Browsers sometimes send what is known as a preflight request to a resource. This is a separate and automatic request that checks if the Access-Control-Allow-Origin is set on the resource before sending the "real" request.

For example, imagine an app that uses the img tag to embed the following image:

https://www.canva.dev/example-assets/image-import/image.jpg

The image will be downloaded with a GET request. Before this GET request is sent though, the browser will send an OPTIONS request to the image to verify that it can be downloaded.

This is important because the backend must set the appropriate origin for the OPTIONS request — not just the GET request. If the preflight request is blocked by CORS, the actual request will never be sent.

If you've set the Access-Control-Allow-Origin header for a resource but the requests are still failing, check if the server is receiving an OPTIONS request. This will help to confirm if it's an preflight request error.

The problem with setting a specific origin is that requests from any other origin will fail. If the backend's endpoints are called by something other than the app's frontend, this isn't what you want.

You can't set multiple origins directly in the Access-Control-Allow-Origin header, but you can:

  1. Create an allowlist of supported origins.
  2. Check if the origin is included in the allowlist.
  3. If the origin is in the allowlist, set the Access-Control-Allow-Origin header to that origin.

In other words, you can dynamically set the header based on the origin.

The following code sample demonstrates how to dynamically set origins with Express.js:

import express from "express";
import cors from "cors";
const CANVA_APP_ID = process.env.CANVA_APP_ID?.toLowerCase(); // The ID must be lowercase
if (!CANVA_APP_ID) {
throw new Error(`CANVA_APP_ID environment variable is not set`);
}
const app = express();
const allowlist = [
`https://app-${CANVA_APP_ID}.canva-apps.com`,
"https://example.com",
];
app.use(
cors({
origin: (origin, callback) => {
if (allowlist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error("Invalid origin"));
}
},
optionsSuccessStatus: 200,
})
);
app.get("/", (req, res) => {
res.send("Hello world");
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
ts

To learn how to configure CORS in a variety of frameworks, see the following (external) documentation: