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:
Generate Code Verifier
Creates a cryptographically random string
Create Challenge
SHA-256 hash of the verifier, base64url encoded
Authorization Request
Redirects to Canva with challenge
User Authorizes
User grants permissions in Canva
Callback
Canva redirects back with authorization code
Token Exchange
Exchange code + verifier for access token
Store Tokens
Save to tokens.json for future use
Scopes & Permissions
Canva Alchemy requests comprehensive permissions:
Design Management
Asset Management
Folder Management
Brand Templates
design:content:read - Read design content
design:content:write - Create and edit designs
design:meta:read - Read design metadata
asset:read - Access your asset library
asset:write - Upload and manage assets
folder:read - Read folder structure
folder:write - Create and organize folders
brandtemplate:meta:read - Read brand template metadata
brandtemplate:content:read - Access brand template content
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
{
"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
Install Bun
curl -fsSL https://bun.sh/install | bash
Or on Windows: powershell - c "irm bun.sh/install.ps1 | iex"
Create Canva App
Go to Canva Developers
Create a new app
Add redirect URI: http://localhost:3057/callback
Copy your Client ID and Client Secret
Configure Environment
Create .env file: CANVA_CLIENT_ID = your_client_id_here
CANVA_CLIENT_SECRET = your_client_secret_here
Start Server
bun run start
# or for dev mode with auto-reload
bun run dev
Usage
First-Time Setup
Start the Server
Server runs on http://localhost:3057
Open in Browser
Navigate to http://localhost:3057
Authorize
Click the authorization link to connect your Canva account
Grant Permissions
Approve the requested scopes in Canva
Done
You’ll be redirected back and tokens will be saved automatically
Making API Calls
Once authorized, the proxy handles authentication:
Get User Info
List Designs
Create Design
Upload Asset
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:
User
Designs
Assets
Folders
Brand Templates
Get current user information GET /api/designs
POST /api/designs
GET /api/designs/:id
PUT /api/designs/:id
DELETE /api/designs/:id
Manage Canva designs GET /api/assets
POST /api/assets
GET /api/assets/:id
DELETE /api/assets/:id
Manage asset library GET /api/folders
POST /api/folders
GET /api/folders/:id
PUT /api/folders/:id
DELETE /api/folders/:id
Organize content in folders GET /api/brand-templates
GET /api/brand-templates/:id
Access brand templates
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
Protect Credentials
Never commit .env or tokens.json to version control: .env
.env.local
tokens.json
Use HTTPS in Production
For production deployments, always use HTTPS and update redirect URIs accordingly
Secure Token Storage
Consider encrypting tokens.json or using a secure credential store
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
Missing credentials error
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