Building a Canva Content App: Working Within the Iframe Sandbox
Learn how to build a Canva content app that respects the iframe sandbox, uses the editor API for design changes, and navigates OAuth, rate limits, and the review pipeline.
17 Sept 2025, 11:26 UTC

The problem: extending Canva without breaking the editor
Canva’s Apps SDK lets third‑party developers add new content types, effects, or publish destinations. The catch is that every app runs inside a sandboxed iframe, communicating with the host editor only through a postMessage bridge. This isolation protects users but forces developers to rethink how they handle assets, authentication, and UI consistency.
Thesis: embrace the sandbox, don’t fight it
Instead of trying to reach out of the iframe, design your app around the capabilities the SDK explicitly exposes: the editor API for design manipulation, the Canva Design System components for UI, and the built‑in asset upload flow. Doing so keeps your app stable across Canva updates and speeds up the review process.
App types and capability scopes
- Content apps – insert elements (images, shapes, embeds) into the current design.
- Effect apps – apply filters or transformations to selected elements.
- Publish apps – export designs to external destinations.
Each type declares a distinct set of permissions in its manifest (e.g., design:content:write for content apps). Requesting only the scopes you need reduces review friction and limits the blast radius of a compromised token.
Worked example: a minimal content app that adds a branded rectangle
Assume you have Node.js 20+ and a Canva developer account. Run the following in a clean directory:
npx @canva/cli create-app my-branded-rect --template content
cd my-branded-rect
npm install
npm run dev
The scaffold includes a manifest.json with the required permissions array. Add the design:content:write scope if it isn’t present. The generated src/App.tsx already imports Canva’s Button and Panel components, so you can build a UI that matches the editor without custom CSS.
Replace the button handler with:
import { editor } from '@canva/design';
async function addBrandedRect() {
const rect = await editor.createElement({
type: 'rectangle',
width: 200,
height: 100,
fill: '#0066ff',
name: 'Branded rectangle'
});
await editor.addElement(rect);
}
When you click the button in the Canva editor (opened via the local tunnel URL shown by npm run dev), the rectangle appears on the canvas. No direct DOM manipulation occurs; the SDK serializes the call and sends it over postMessage to the host.
Trade‑offs and limitations
- Iframe sandbox – clipboard, file‑system, and certain browser APIs are blocked unless you declare them in
manifest.json(e.g.,clipboard-read). Plan your UX around these constraints. - Rate limits – the platform enforces ~100 requests/second per app per user. Exceeding returns HTTP 429 with a
Retry-Afterheader; implement exponential back‑off in your backend. - Review latency – every version update goes through Canva’s security review (typically 2‑5 business days). Batch non‑urgent changes to avoid repeated cycles.
Actionable next steps
- Scaffold a sample app with
npx @canva/cli create-appand verify the local tunnel loads in the Canva editor. - Add only the scopes your feature needs; test token refresh using the OAuth 2.0 PKCE flow documented at developers.canva.com.
- Monitor the Canva Developer Changelog for breaking changes – especially updates to the
editorAPI or manifest schema. - Before submitting, run the automated linting step (
npm run lint) and confirm the app passes the staged review checklist in the developer console.
By designing within the sandbox rather than around it, you’ll ship faster, stay compatible, and give Canva users a seamless extension experience.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.