Cross-Origin Resource Sharing
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:
- Preview the app in the Canva editor.
- Open the JavaScript Console via the browser's developer tools.
- 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 lowercaseif (!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}`);});
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:
- Create an allowlist of supported origins.
- Check if the origin is included in the allowlist.
- 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 lowercaseif (!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}`);});
Framework integrations
To learn how to configure CORS in a variety of frameworks, see the following (external) documentation: