From canva-pack
Provides Canva Connect API architecture blueprints for monolith, service layer, and microservice scales using TypeScript, REST, and OAuth PKCE. Use for new integrations or migrations.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin canva-packThis skill is limited to using the following tools:
Three validated architecture patterns for Canva Connect API integrations. All use the REST API at `api.canva.com/rest/v1/*` with OAuth 2.0 PKCE tokens. The key architectural decision is how to handle token storage, async operations (exports, autofills), and rate limit management.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Three validated architecture patterns for Canva Connect API integrations. All use the REST API at api.canva.com/rest/v1/* with OAuth 2.0 PKCE tokens. The key architectural decision is how to handle token storage, async operations (exports, autofills), and rate limit management.
Best for: MVPs, small teams, < 100 Canva users
my-app/
├── src/
│ ├── canva/
│ │ ├── client.ts # REST client with auto-refresh
│ │ ├── auth.ts # OAuth PKCE flow
│ │ └── types.ts
│ ├── routes/
│ │ ├── auth.ts # OAuth callback
│ │ └── designs.ts # Design CRUD
│ ├── store/
│ │ └── tokens.ts # SQLite/file token store
│ └── index.ts
// Direct API calls in route handlers
app.post('/api/designs', async (req, res) => {
const canva = getClientForUser(req.user.id);
const { design } = await canva.request('/designs', {
method: 'POST',
body: JSON.stringify({
design_type: { type: 'custom', width: 1080, height: 1080 },
title: req.body.title,
}),
});
res.json({ designId: design.id, editUrl: design.urls.edit_url });
});
Pros: Fast to build, simple token management, easy to debug. Cons: Synchronous exports block requests, no job queue for autofills.
Best for: Growing apps, 100-1,000 users, multiple Canva features
my-app/
├── src/
│ ├── canva/
│ │ ├── client.ts
│ │ └── auth.ts
│ ├── services/
│ │ ├── design.service.ts # Business logic + caching
│ │ ├── export.service.ts # Async export with polling
│ │ ├── asset.service.ts # Upload management
│ │ └── template.service.ts # Autofill orchestration
│ ├── queue/
│ │ └── export-worker.ts # Background export processing
│ ├── routes/
│ └── store/
│ └── tokens.ts # PostgreSQL encrypted tokens
// Service layer handles caching, retry, and async operations
class ExportService {
constructor(
private canva: CanvaClient,
private cache: Redis,
private queue: Bull.Queue
) {}
async exportDesign(designId: string, format: object): Promise<string> {
// Check cache for recent export
const cached = await this.cache.get(`export:${designId}:${JSON.stringify(format)}`);
if (cached) return cached;
// Queue export job — don't block the request
const job = await this.queue.add('canva-export', { designId, format });
return job.id;
}
}
// Background worker polls Canva export API
exportQueue.process('canva-export', async (job) => {
const { designId, format } = job.data;
const canva = await getServiceClient();
const { job: exportJob } = await canva.request('/exports', {
method: 'POST',
body: JSON.stringify({ design_id: designId, format }),
});
// Poll for completion
let result = exportJob;
while (result.status === 'in_progress') {
await new Promise(r => setTimeout(r, 2000));
const poll = await canva.request(`/exports/${result.id}`);
result = poll.job;
}
return result.status === 'success' ? result.urls : null;
});
Pros: Non-blocking exports, caching, separation of concerns. Cons: More infrastructure (Redis, job queue), more complex deployment.
Best for: 1,000+ users, multi-team, strict SLAs, Canva Enterprise with autofill
canva-service/ # Dedicated microservice
├── src/
│ ├── api/
│ │ └── grpc/ # Internal gRPC API
│ ├── canva/
│ │ ├── client.ts
│ │ └── auth.ts
│ ├── services/
│ ├── workers/
│ │ ├── export.worker.ts
│ │ ├── autofill.worker.ts
│ │ └── webhook.worker.ts
│ └── store/
│ └── tokens.ts # Vault-backed token storage
├── k8s/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── hpa.yaml # Scale based on queue depth
Key differences:
| Factor | Monolith | Service Layer | Microservice |
|---|---|---|---|
| Users | < 100 | 100-1,000 | 1,000+ |
| Team Size | 1-3 | 3-10 | 10+ |
| Export Volume | < 100/day | 100-2,000/day | 2,000-5,000/day |
| Canva Tier | Free/Pro | Pro/Teams | Enterprise |
| Infrastructure | Single server | App + Redis + queue | Kubernetes |
| Time to Build | 1-2 days | 1-2 weeks | 2-4 weeks |
Monolith → Service Layer:
1. Extract canva/ to services/
2. Add Redis for caching
3. Add BullMQ for async exports
4. Move token store to PostgreSQL
Service Layer → Microservice:
1. Create canva-service repository
2. Define gRPC contract
3. Add per-operation workers
4. Deploy to Kubernetes
5. Migrate token store to Vault
| Issue | Cause | Solution |
|---|---|---|
| Over-engineering | Wrong variant | Start simpler, migrate when needed |
| Export blocking requests | No job queue (Variant A) | Queue with BullMQ |
| Token management complex | Multi-user | Use factory pattern per user |
| Integration export quota | > 5,000/day | Contact Canva for increase |
For common anti-patterns, see canva-known-pitfalls.