Skip to content
Skillv1.0.0

canva-reference-architecture

Implement Canva Connect API reference architecture with best-practice project layout. Use when designing new Canva integrations, reviewing project structure, or establishing architecture standards for

by jeremylongshore(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from jeremylongshore/tons-of-skills-marketplace (plugins/saas-packs/canva-pack/skills/canva-reference-architecture/SKILL.md). Install upstream with npx skills add jeremylongshore/tons-of-skills-marketplace --skill canva-reference-architecture. Copyright stays with the author (MIT).

Canva Reference Architecture

Overview

Production-ready architecture for Canva Connect API integrations. All interactions use the REST API at api.canva.com/rest/v1/* with OAuth 2.0 PKCE authentication.

Prerequisites

  • Server-side OAuth application, reviewed scopes, tenant/asset authorization model, and protected secrets/data stores.
  • Mocked tests plus a dedicated synthetic-asset integration environment.

Instructions

  1. Put caller authorization, asset-rights checks, input validation, and idempotency before the Canva adapter.
  2. Keep OAuth/token exchange and signed URLs server-side, minimize stored/exported fields, and encrypt controlled artifacts.
  3. Process webhooks through signature verification and durable queues, then recheck policy before side effects.
  4. Separate liveness from readiness and pause mutations whenever authorization, reconciliation, or provider health is uncertain.

Project Structure

my-canva-integration/
├── src/
│   ├── canva/
│   │   ├── client.ts           # REST client wrapper with auto-refresh
│   │   ├── auth.ts             # OAuth 2.0 PKCE flow
│   │   ├── types.ts            # API request/response TypeScript types
│   │   └── errors.ts           # CanvaAPIError class
│   ├── services/
│   │   ├── design.service.ts   # Design creation, export, listing
│   │   ├── asset.service.ts    # Asset upload and management
│   │   ├── template.service.ts # Brand template autofill (Enterprise)
│   │   └── folder.service.ts   # Folder management
│   ├── routes/
│   │   ├── auth.ts             # OAuth callback endpoints
│   │   ├── designs.ts          # Design CRUD routes
│   │   ├── exports.ts          # Export trigger/download routes
│   │   └── webhooks.ts         # Webhook receiver
│   ├── middleware/
│   │   ├── auth.ts             # Verify user has valid Canva token
│   │   └── rate-limit.ts       # Client-side rate limit guard
│   ├── store/
│   │   └── tokens.ts           # Encrypted token storage (DB)
│   └── index.ts
├── tests/
│   ├── mocks/
│   │   └── canva-server.ts     # MSW mock server
│   ├── unit/
│   │   └── design.service.test.ts
│   └── integration/
│       └── canva-api.test.ts
├── .env.example
└── package.json

Layer Architecture

┌─────────────────────────────────────────┐
│             Routes Layer                │
│   (Express/Next.js — HTTP in/out)       │
├─────────────────────────────────────────┤
│           Service Layer                 │
│  (Business logic, caching, validation)  │
├─────────────────────────────────────────┤
│          Canva Client Layer             │
│   (REST calls, token refresh, retry)    │
├─────────────────────────────────────────┤
│         Infrastructure Layer            │
│    (Token store, cache, queue)          │
└─────────────────────────────────────────┘

Service Layer Pattern

// src/services/design.service.ts
import { CanvaClient } from '../canva/client';
import { LRUCache } from 'lru-cache';

export class DesignService {
  private cache = new LRUCache<string, any>({ max: 200, ttl: 300_000 });

  constructor(private canva: CanvaClient) {}

  async create(opts: {
    type: 'preset' | 'custom';
    name?: string;
    width?: number;
    height?: number;
    title: string;
    assetId?: string;
  }) {
    const designType = opts.type === 'preset'
      ? { type: 'preset' as const, name: opts.name! }
      : { type: 'custom' as const, width: opts.width!, height: opts.height! };

    return this.canva.request('/designs', {
      method: 'POST',
      body: JSON.stringify({
        design_type: designType,
        title: opts.title,
        ...(opts.assetId && { asset_id: opts.assetId }),
      }),
    });
  }

  async get(id: string) {
    const cached = this.cache.get(id);
    if (cached) return cached;

    const result = await this.canva.request(`/designs/${id}`);
    this.cache.set(id, result);
    return result;
  }

  async export(designId: string, format: object): Promise<string[]> {
    // Start export job
    const { job } = await this.canva.request('/exports', {
      method: 'POST',
      body: JSON.stringify({ design_id: designId, format }),
    });

    // Poll for completion
    return this.pollExport(job.id);
  }

  private async pollExport(exportId: string, timeoutMs = 60000): Promise<string[]> {
    const start = Date.now();
    while (Date.now() - start < timeoutMs) {
      const { job } = await this.canva.request(`/exports/${exportId}`);
      if (job.status === 'success') return job.urls;
      if (job.status === 'failed') throw new Error(`Export failed: ${job.error?.message}`);
      await new Promise(r => setTimeout(r, 2000));
    }
    throw new Error('Export timeout');
  }
}

Data Flow

User clicks "Create Design"
       │
       ▼
┌─────────────┐
│   Route     │  POST /api/designs
│   Handler   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Design     │  Validates input, checks auth
│  Service    │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Canva      │  POST api.canva.com/rest/v1/designs
│  Client     │  (auto-refreshes token if expired)
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Canva      │  Returns design.id, edit_url, view_url
│  API        │
└─────────────┘
       │
       ▼
  Redirect user to edit_url → Canva Editor

Auth Middleware

// src/middleware/auth.ts
export function requireCanvaAuth(tokenStore: TokenStore) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ error: 'Not authenticated' });

    const tokens = await tokenStore.get(userId);
    if (!tokens) return res.status(403).json({ error: 'Canva not connected' });

    // Attach client to request for downstream use
    req.canva = new CanvaClient({
      clientId: process.env.CANVA_CLIENT_ID!,
      clientSecret: process.env.CANVA_CLIENT_SECRET!,
      tokens,
      onTokenRefresh: (newTokens) => tokenStore.save(userId, newTokens),
    });

    next();
  };
}

Output

The architecture record identifies scope, authorization boundary, protected data stores, event/reconciliation flow, deployment version, and rollback path. It distinguishes mocked from device/API integration evidence and excludes tokens, design content, and signed URLs.

Examples

Run an API service with server-side OAuth, a policy-resolved synthetic test asset, and an encrypted metadata-only store. A worker records an idempotency key before export, validates the authorized result, and pauses its queue if readiness or policy checks fail.

Error Handling

Issue Cause Solution
Circular dependencies Wrong layering Services import client, not vice versa
Token not found User hasn't connected Canva Redirect to OAuth flow
Cache stale Design updated in Canva Invalidate on webhook events
Service timeout Export taking too long Increase timeout, add job queue

Resources

Next Steps

For multi-environment setup, see canva-multi-env-setup.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/jeremylongshore-tons-of-skills-marketplace-canva-referen-c5bc5e/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

jeremylongshore-tons-of-skills-marketplace-canva-referen-c5bc5e.ocm.jsonjson
{
  "ocm": "1",
  "id": "jeremylongshore-tons-of-skills-marketplace-canva-referen-c5bc5e",
  "kind": "skill",
  "name": "canva-reference-architecture",
  "description": "Implement Canva Connect API reference architecture with best-practice project layout. Use when designing new Canva integrations, reviewing project structure, or establishing architecture standards for Canva applications. Trigger with phrases like \"canva architecture\", \"canva project structure\", \"how to organize canva\", \"canva layout\", \"canva reference\".",
  "publisher": "jeremylongshore",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "saas",
      "design",
      "canva",
      "skills-sh"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Implement Canva Connect API reference architecture with best-practice project layout. Use when designing new Canva integrations, reviewing project structure, or establishing architecture standards for Canva applications. Trigger with phrases like \"canva architecture\", \"canva project structure\", \"how to organize canva\", \"canva layout\", \"canva reference\"."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "skills.sh",
      "repository": "https://github.com/jeremylongshore/tons-of-skills-marketplace",
      "path": "plugins/saas-packs/canva-pack/skills/canva-reference-architecture/SKILL.md",
      "ref": "HEAD",
      "url": "https://github.com/jeremylongshore/tons-of-skills-marketplace/blob/HEAD/plugins/saas-packs/canva-pack/skills/canva-reference-architecture/SKILL.md",
      "key": "jeremylongshore/tons-of-skills-marketplace/plugins/saas-packs/canva-pack/skills/canva-reference-architecture/SKILL.md"
    },
    "compatibility": "Designed for Claude Code",
    "allowed_tools": [
      "Read,",
      "Grep"
    ],
    "license": "MIT"
  },
  "instructions": "# Canva Reference Architecture\n\n## Overview\n\nProduction-ready architecture for Canva Connect API integrations. All interactions use the REST API at `api.canva.com/rest/v1/*` with OAuth 2.0 PKCE authentication.\n\n## Prerequisites\n\n- Server-side OAuth application, reviewed scopes, tenant/asset authorization model, and protected secrets/data stores.\n- Mocked tests plus a dedicated synthetic-asset integration environment.\n\n## Instructions\n\n1. Put caller authorization, asset-rights checks, input validation, and idempotency before the Canva adapter.\n2. Keep OAuth/token exchange and signed URLs server",
  "cost": {
    "context_tokens": 1798
  }
}

Fetch it by URL: GET /api/v1/registry/jeremylongshore-tons-of-skills-marketplace-canva-referen-c5bc5e/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.