On September 25th, 2024, we released v2 of the Apps SDK. To learn what’s new and how to upgrade, see Migration FAQ and Migration guide.

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(opens in a new tab or window) or requests to load third-party assets, such as thumbnail images.

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

CORS is a large topic, and it's beyond the scope of this documentation to explain every possible nuance. If you need more assistance, check out the Cross-Origin Resource Sharing(opens in a new tab or window) page on MDN.

What is 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 you explicitly configure the backend to support CORS. This is an unavoidable feature of all modern web browsers.

Identifying CORS errors

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 a CORS testing tool to check the CORS headers on a particular endpoint.

Fixing CORS errors

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

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

CANVA_APP_ID is a placeholder for the app's ID and must be provided in lowercase. To get the ID of an app, visit the Your apps(opens in a new tab or window) page in the Developer Portal. The ID is listed in the App ID column.

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

Handling preflight requests

Browsers sometimes send what is known as a preflight request(opens in a new tab or window) 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 a preflight request error.

A preflight request may be sent for various types of requests — not just GET requests.

Handling multiple origins

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

Framework integrations

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