Set up a Development URL (with TypeScript)

Learn how to set up a Development URL with webpack and TypeScript.

In the Developer Portal, some extensions have an Extension source field. This field determines where Canva loads the extension from.

The field has two options:

  • JavaScript file

  • Development URL

By providing a Development URL, you can make changes to an extension and see the results of those changes by simply closing and reopening the extension in the editor.

This tutorial explains how to set up a Development URL with TypeScript and webpack.

If you're developing an extension with JavaScript instead of TypeScript, refer to Set up a Development URL (with JavaScript).

Step 1: Create a project

To begin, set up a project with the following structure:

  • my-app/

    • dist/

    • src/

      • index.html

      • index.ts

    • package.json

    • tsconfig.json

    • webpack.config.ts

To set up this structure, run the following commands:

mkdir my-app my-app/src my-app/dist
cd my-app
npm init --yes # yarn init --yes
touch src/index.html src/index.ts tsconfig.json webpack.config.ts

The touch command is not available on Windows. If you're using Windows, create the files manually.

Then copy the following code into the src/index.html file:

<!DOCTYPE html>
<html style="max-width: 100%; max-height: 100%; overflow: hidden">
<body style="max-width: 100%; max-height: 100%; margin: 0">
<script>
// Get the URL and version number of JavaScript client
const params = new URLSearchParams(window.location.search);
const baseUrl = params.get('libBase');
const version = params.get('lib');
// Construct the URL of the JavaScript client
const libUrl = baseUrl + version + '.js';
// Load the JavaScript client
const lib = document.createElement('script');
lib.src = libUrl;
lib.onload = () => {
// Load the JavaScript bundle for the extension
const extension = document.createElement('script');
extension.src = 'main.js';
document.body.appendChild(extension);
};
document.head.appendChild(lib);
</script>
</body>
</html>

When Canva loads an extension via the Development URL, this code loads the JavaScript client that lets the extension interact with the Canva editor.

While you're developing an extension with a Development URL, your extension can send arbitrary HTTP requests to non-Canva domains. When you upload a JavaScript file though, those requests will fail.

Step 2: Install the dependencies

In the project's directory, install the following dependencies:

To install these dependencies, run the following command:

npm install @canva/editing-extensions-api-typings @types/react @types/webpack @types/webpack-dev-server ts-loader ts-node typescript webpack webpack-cli webpack-dev-server --save-dev
# yarn add @canva/editing-extensions-api-typings @types/react @types/webpack @types/webpack-dev-server ts-loader ts-node typescript webpack webpack-cli webpack-dev-server --dev

These commands install the latest versions of these dependencies, which may have breaking changes that cause issues when setting up a Development URL. Refer to the package.json file for versions that are confirmed to work.

The @types/react dependency is only required for rendering rich controls with JSX.

(Optional) Step 3: Configure TypeScript

You don't need to provide a custom configuration for TypeScript. The default configuration works fine. If you want to render rich controls with JSX though, you must set the jsx option in the tsconfig.json file to "react":

{
"compilerOptions": {
"jsx": "react"
}
}

Step 4: Configure webpack

Copy the following code into the webpack.config.ts file:

import * as path from 'path';
import * as webpack from 'webpack';
const DIST_DIR = path.resolve(__dirname, 'dist');
const SRC_DIR = path.resolve(__dirname, 'src');
const config: webpack.Configuration = {
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
allowTsInNodeModules: true,
},
},
],
},
],
},
resolve: {
extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'],
},
devServer: {
contentBase: [DIST_DIR, SRC_DIR],
https: true,
port: 9090,
},
};
export default config;

This configuration:

  • Uses TypeScript instead of JavaScript (although JavaScript would also work).

  • Enables the allowTsInNodeModules option for ts-loader.

  • Resolves files with TypeScript and JavaScript extensions.

  • Runs the local development server over HTTPS.

  • Serves content from the dist and src directory.

If you plan on changing the webpack or TypeScript configuration, make the changes incrementally and test that the build still compiles after each change.

Step 5: Create "build" and "start" scripts

Add the following scripts to the package.json file:

"scripts": {
"build": "webpack --config ./webpack.config.ts --mode production",
"start": "webpack serve --config ./webpack.config.ts --mode development"
}

To build a JavaScript bundle, run the following command:

# Build a JavaScript bundle
npm run build # yarn build

To start a local development server, run the following command:

# Start a local development server
npm run start # yarn start

By default, webpack builds a main.js file to the dist directory.

Step 6: Bypass the invalid security certificate

If you start the local development server and navigate to https://localhost:9090, the following warning appears:

NET::ERR_CERT_AUTHORITY_INVALID

This is because the server doesn't have a valid security certificate.

Because of this warning, Canva won't load the extension and you can't preview it. You know the server is secure though, since you're the one who set it up, so it's okay to bypass the warning.

To bypass the warning in Google Chrome:

  1. Start the development server.

  2. Select Advanced.

  3. Select Proceed to localhost (unsafe).

A blank page should appear.

You need to bypass this warning every time you start the server.

You can use ngrok to expose a local server via a public, HTTPS-enabled URL. This provides a valid security certificate and prevents the warning from occurring.

Step 7: Preview the extension

To verify that the Development URL is working as expected:

  1. Create an extension via the Developer Portal.

  2. Add some code to the src/index.ts file.

  3. In the Development URL field, enter the URL of the server.

  4. Select Preview.

  5. Select the extension.

  6. When the Canva editor loads, select Connect.

If the extension doesn't load, check the JavaScript Console for errors.

Example

src/index.html

<!DOCTYPE html>
<html style="max-width: 100%; max-height: 100%; overflow: hidden">
<head>
<meta
http-equiv="Content-Security-Policy"
content="script-src https://apps.canva-apps.com; connect-src data: https://apps.canva-apps.com https://image-manipulation.canva.com https://image.canva.com https://media-private.canva.com https://media-public.canva.com https://s3.amazonaws.com/image-manipulation.canva.com/ https://s3.amazonaws.com/image.canva.com/ https://s3.amazonaws.com/media-private.canva.com/ https://s3.amazonaws.com/media-public.canva.com/; form-action 'none';"
/>
</head>
<body style="max-width: 100%; max-height: 100%; margin: 0">
<script>
// Get the URL and version number of JavaScript client
const params = new URLSearchParams(window.location.search);
const baseUrl = params.get('libBase');
const version = params.get('lib');
// Construct the URL of the JavaScript client
const libUrl = baseUrl + version + '.js';
// Load the JavaScript client
const lib = document.createElement('script');
lib.src = libUrl;
lib.onload = () => {
// Load the JavaScript bundle for the extension
const extension = document.createElement('script');
extension.src = 'main.js';
document.body.appendChild(extension);
};
document.head.appendChild(lib);
</script>
</body>
</html>

src/index.ts

import { CanvaApiClient } from '@canva/editing-extensions-api-typings';
const canva: CanvaApiClient = window.canva.init();
canva.onReady(async (opts) => {
console.log(opts);
});

package.json

{
"name": "my-app",
"version": "1.0.0",
"devDependencies": {
"@canva/editing-extensions-api-typings": "^0.0.78-alpha.0",
"@types/react": "^16.7.20",
"@types/webpack": "^4.41.22",
"@types/webpack-dev-server": "^3.11.0",
"ts-loader": "^8.0.4",
"ts-node": "^9.0.0",
"typescript": "3.8.3",
"webpack": "^5.1.0",
"webpack-cli": "^4.0.0",
"webpack-dev-server": "^3.11.0"
},
"scripts": {
"build": "webpack --config ./webpack.config.ts --mode production",
"start": "webpack serve --config ./webpack.config.ts --mode development"
}
}

tsconfig.json

{
"compilerOptions": {
"jsx": "react"
}
}

webpack.config.ts

import * as path from 'path';
import * as webpack from 'webpack';
const DIST_DIR = path.resolve(__dirname, 'dist');
const SRC_DIR = path.resolve(__dirname, 'src');
const config: webpack.Configuration = {
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
allowTsInNodeModules: true,
},
},
],
},
],
},
resolve: {
extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'],
},
devServer: {
contentBase: [DIST_DIR, SRC_DIR],
https: true,
port: 9090,
},
};
export default config;