Skillshub canva-core-workflow-a
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/canva-core-workflow-a" ~/.claude/skills/comeonoliver-skillshub-canva-core-workflow-a && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/canva-core-workflow-a/SKILL.mdsource content
Canva Core Workflow A — Design Creation & Export
Overview
The primary Canva integration workflow: create designs via the REST API, let users edit them in Canva's editor, then export finished designs as PDF/PNG/JPG for downstream use (email campaigns, social posts, print orders).
Prerequisites
- Completed
setup with valid access tokencanva-install-auth - Scopes:
,design:content:write
,design:content:readdesign:meta:read
Instructions
Step 1: Create a Design
// POST https://api.canva.com/rest/v1/designs // Rate limit: 20 req/min per user // Scope: design:content:write interface CreateDesignRequest { design_type: | { type: 'preset'; name: 'doc' | 'whiteboard' | 'presentation' } | { type: 'custom'; width: number; height: number }; // 40-8000 px title?: string; // 1-255 characters asset_id?: string; // Image asset to insert } // Create a social media post (custom dimensions) const { design } = await canvaAPI('/designs', token, { method: 'POST', body: JSON.stringify({ design_type: { type: 'custom', width: 1080, height: 1080 }, title: 'Instagram Post — Q1 Campaign', }), }); // design.id — unique identifier for all future operations // design.urls.edit_url — redirect user here to edit (expires 30 days) // design.urls.view_url — read-only link (expires 30 days) // design.thumbnail.url — preview image (expires 15 minutes)
Note: Blank designs are auto-deleted after 7 days if never edited.
Step 2: Redirect User to Edit
// Redirect the user to Canva's editor // The edit_url is user-specific and expires after 30 days res.redirect(design.urls.edit_url);
Step 3: Get Design Metadata
// GET https://api.canva.com/rest/v1/designs/{designId} // Rate limit: 100 req/min per user // Scope: design:meta:read const { design: meta } = await canvaAPI(`/designs/${designId}`, token); console.log(`Title: ${meta.title}`); console.log(`Pages: ${meta.page_count}`); console.log(`Created: ${new Date(meta.created_at * 1000).toISOString()}`); console.log(`Updated: ${new Date(meta.updated_at * 1000).toISOString()}`); console.log(`Owner: user=${meta.owner.user_id}, team=${meta.owner.team_id}`);
Step 4: Export the Finished Design
// POST https://api.canva.com/rest/v1/exports // Rate limits: // Per user: 75 exports/5min, 500/24hr // Per integration: 750 exports/5min, 5000/24hr // Per document: 75 exports/5min // Scope: design:content:read // Export as high-quality PDF const { job } = await canvaAPI('/exports', token, { method: 'POST', body: JSON.stringify({ design_id: designId, format: { type: 'pdf', size: 'a4', // a4 | a3 | letter | legal (Docs only) export_quality: 'pro', // regular | pro }, }), }); // Export as PNG with transparent background const { job: pngJob } = await canvaAPI('/exports', token, { method: 'POST', body: JSON.stringify({ design_id: designId, format: { type: 'png', width: 1200, // 40-25000 px transparent_background: true, lossless: true, as_single_image: false, // true = merge all pages into one image }, }), }); // Export specific pages as JPG const { job: jpgJob } = await canvaAPI('/exports', token, { method: 'POST', body: JSON.stringify({ design_id: designId, format: { type: 'jpg', quality: 85, // 1-100 pages: [1, 2], // specific page numbers }, }), });
Step 5: Poll for Export Completion
// GET https://api.canva.com/rest/v1/exports/{exportId} async function waitForExport( exportId: string, token: string, maxWaitMs = 60000 ): Promise<string[]> { const start = Date.now(); while (Date.now() - start < maxWaitMs) { const { job } = await canvaAPI(`/exports/${exportId}`, token); if (job.status === 'success') { return job.urls; // Array of download URLs, valid 24 hours } if (job.status === 'failed') { // Error codes: license_required | approval_required | internal_failure throw new Error(`Export failed: ${job.error.code} — ${job.error.message}`); } await new Promise(r => setTimeout(r, 2000)); // Poll every 2 seconds } throw new Error('Export timed out'); } const downloadUrls = await waitForExport(job.id, token);
Supported Export Formats
| Format | Type | Key Options |
|---|---|---|
| , , | |
| PNG | | , , , , |
| JPG | | (1-100), , |
| PPTX | | |
| GIF | | , , |
| MP4 | | (horizontal_480p, 720p, 1080p, 4k) |
Error Handling
| Error | Cause | Solution |
|---|---|---|
| 400 Bad Request | Invalid dimensions or format | Check min/max values |
| 401 Unauthorized | Token expired | Refresh via OAuth |
| 403 Forbidden | Missing scope | Enable |
| 404 Not Found | Design deleted or not owned | Verify design ID |
| 429 Rate Limited | Too many exports | Respect header |
| Design uses premium elements | User needs Canva Pro |
Resources
Next Steps
For asset management and brand template autofill, see
canva-core-workflow-b.