Skip to main content

Canva Alchemy

Local Canva API proxy & UI by Digital Alchemy A TypeScript-powered OAuth2 PKCE proxy server that connects you to the Canva Connect API, enabling programmatic access to create, edit, and automate Canva designs.

Overview

Canva Alchemy handles the complex OAuth2 authentication flow for Canva’s API, stores your tokens securely, and provides a clean local interface for interacting with your Canva account.

OAuth2 PKCE

Secure authentication flow with automatic token refresh

Local Proxy

Runs on localhost:3057 - no external servers needed

Full API Access

Complete access to Canva Connect API endpoints

Token Management

Automatic storage and refresh of access tokens

Features

OAuth2 PKCE Flow

Implements the full OAuth2 Authorization Code flow with PKCE (Proof Key for Code Exchange) for maximum security:
1

Generate Code Verifier

Creates a cryptographically random string
2

Create Challenge

SHA-256 hash of the verifier, base64url encoded
3

Authorization Request

Redirects to Canva with challenge
4

User Authorizes

User grants permissions in Canva
5

Callback

Canva redirects back with authorization code
6

Token Exchange

Exchange code + verifier for access token
7

Store Tokens

Save to tokens.json for future use

Scopes & Permissions

Canva Alchemy requests comprehensive permissions:
  • design:content:read - Read design content
  • design:content:write - Create and edit designs
  • design:meta:read - Read design metadata

Token Management

Automated token lifecycle:
interface TokenData {
  access_token: string;
  refresh_token: string;
  expires_at: number; // Unix timestamp
}

// Automatically refreshes when needed
async function ensureValidToken(): Promise<string> {
  if (!tokens || Date.now() >= tokens.expires_at) {
    await refreshAccessToken();
  }
  return tokens.access_token;
}
Tokens are stored in tokens.json in the project directory. Keep this file secure and add it to .gitignore.

Tech Stack

package.json
{
  "name": "canva-alchemy",
  "version": "1.0.0",
  "description": "Canva Alchemy - Local Canva API proxy & UI by Digital Alchemy",
  "scripts": {
    "start": "bun run canva-server.ts",
    "dev": "bun --watch canva-server.ts"
  }
}

Bun Runtime

Lightning-fast JavaScript runtime with built-in TypeScript support

TypeScript

Type-safe development with full IntelliSense

Native HTTP

Uses Bun’s native HTTP server for performance

Canva Connect API

Official Canva REST API v1

Installation

1

Install Bun

curl -fsSL https://bun.sh/install | bash
Or on Windows:
powershell -c "irm bun.sh/install.ps1 | iex"
2

Create Canva App

  1. Go to Canva Developers
  2. Create a new app
  3. Add redirect URI: http://localhost:3057/callback
  4. Copy your Client ID and Client Secret
3

Configure Environment

Create .env file:
CANVA_CLIENT_ID=your_client_id_here
CANVA_CLIENT_SECRET=your_client_secret_here
4

Start Server

bun run start
# or for dev mode with auto-reload
bun run dev

Usage

First-Time Setup

1

Start the Server

bun run start
Server runs on http://localhost:3057
2

Open in Browser

Navigate to http://localhost:3057
3

Authorize

Click the authorization link to connect your Canva account
4

Grant Permissions

Approve the requested scopes in Canva
5

Done

You’ll be redirected back and tokens will be saved automatically

Making API Calls

Once authorized, the proxy handles authentication:
const response = await fetch('http://localhost:3057/api/user');
const user = await response.json();
console.log(user);

Architecture

Server Structure

// Core constants
const CANVA_API_BASE = "https://api.canva.com/rest/v1";
const CANVA_AUTH_URL = "https://www.canva.com/api/oauth/authorize";
const CANVA_TOKEN_URL = `${CANVA_API_BASE}/oauth/token`;
const PORT = 3057;
const REDIRECT_URI = `http://localhost:${PORT}/callback`;

// OAuth2 helpers
function generateCodeVerifier(): string;
function generateCodeChallenge(verifier: string): string;
function generateState(): string;

// Token management
async function loadTokens(): Promise<TokenData | null>;
async function saveTokens(tokens: TokenData): Promise<void>;
async function refreshAccessToken(): Promise<void>;

// HTTP server
Bun.serve({
  port: PORT,
  async fetch(req) {
    const url = new URL(req.url);
    
    // Routes:
    // GET  / - Home page with auth link
    // GET  /auth - Start OAuth flow
    // GET  /callback - OAuth callback handler
    // GET  /api/* - Proxy to Canva API
    // POST /api/* - Proxy to Canva API
  }
});

OAuth Flow Implementation

// Step 1: Start authorization
app.get('/auth', (req, res) => {
  const verifier = generateCodeVerifier();
  const challenge = generateCodeChallenge(verifier);
  const state = generateState();
  
  // Store for callback
  pendingVerifier = verifier;
  pendingState = state;
  
  const authUrl = new URL(CANVA_AUTH_URL);
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('scope', SCOPES);
  authUrl.searchParams.set('state', state);
  authUrl.searchParams.set('code_challenge', challenge);
  authUrl.searchParams.set('code_challenge_method', 'S256');
  
  res.redirect(authUrl.toString());
});

// Step 2: Handle callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // Verify state
  if (state !== pendingState) {
    throw new Error('Invalid state');
  }
  
  // Exchange code for token
  const response = await fetch(CANVA_TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code,
      code_verifier: pendingVerifier,
      redirect_uri: REDIRECT_URI
    })
  });
  
  const data = await response.json();
  await saveTokens({
    access_token: data.access_token,
    refresh_token: data.refresh_token,
    expires_at: Date.now() + (data.expires_in * 1000)
  });
  
  res.redirect('/');
});

API Endpoints

The proxy exposes Canva API endpoints:
GET /api/user
Get current user information

Use Cases

Batch Design Creation

Generate hundreds of social media posts from templates

Dynamic Content

Personalize designs with user data or API responses

Design Automation

Automate repetitive design tasks and workflows

Asset Management

Programmatically organize and upload brand assets

Integration

Connect Canva to your existing tools and workflows

Reporting

Generate branded reports and presentations automatically

Security Best Practices

1

Protect Credentials

Never commit .env or tokens.json to version control:
.env
.env.local
tokens.json
2

Use HTTPS in Production

For production deployments, always use HTTPS and update redirect URIs accordingly
3

Secure Token Storage

Consider encrypting tokens.json or using a secure credential store
4

Rotate Secrets

Regularly rotate your Canva app credentials
This tool runs locally and stores tokens in plain text. For production use, implement proper encryption and secret management.

Troubleshooting

Ensure .env file exists with:
CANVA_CLIENT_ID=your_client_id
CANVA_CLIENT_SECRET=your_client_secret
In your Canva app settings, add:
http://localhost:3057/callback
Must match exactly (including port).
Delete tokens.json and re-authorize:
rm tokens.json
bun run start
# Visit http://localhost:3057 and authorize again
Install Bun:
curl -fsSL https://bun.sh/install | bash
Restart your terminal after installation.

Example: Batch Create Instagram Posts

const templates = [
  { text: 'Monday Motivation', color: '#FF6B6B' },
  { text: 'Tuesday Tips', color: '#4ECDC4' },
  { text: 'Wednesday Wisdom', color: '#45B7D1' },
];

for (const template of templates) {
  const response = await fetch('http://localhost:3057/api/designs', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      title: template.text,
      type: 'Instagram Post',
      width: 1080,
      height: 1080,
      elements: [
        {
          type: 'text',
          text: template.text,
          color: template.color,
          fontSize: 72,
          x: 540,
          y: 540,
          align: 'center'
        }
      ]
    })
  });
  
  const design = await response.json();
  console.log(`Created: ${design.id}`);
}

Get Canva API Credentials

Sign up for Canva Developer access to get started

Build docs developers (and LLMs) love