Skip to main content
RapidDev - Software Development Agency
bubble-integrationsBubble API Connector

Canva

Connect Bubble to Canva's Connect API using OAuth 2.0 with PKCE. Build a Backend Workflow that exchanges the authorization code for access and refresh tokens, stores them on the User record, and refreshes them automatically before expiry. API Connector calls with a Private Authorization header keep your tokens server-side, letting team members browse brand assets in a Bubble repeating group without a Canva seat.

What you'll learn

  • How to register a Canva Connect API app and get OAuth credentials
  • How to build an OAuth 2.0 PKCE consent flow inside Bubble without a native handler
  • How to store and automatically refresh Canva access and refresh tokens in Bubble
  • How to create API Connector calls for Canva designs with Private Authorization headers
  • How to build a brand-asset library repeating group with filtered thumbnails
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced15 min read3–4 hoursMedia & ContentLast updated July 2026RapidDev Engineering Team
TL;DR

Connect Bubble to Canva's Connect API using OAuth 2.0 with PKCE. Build a Backend Workflow that exchanges the authorization code for access and refresh tokens, stores them on the User record, and refreshes them automatically before expiry. API Connector calls with a Private Authorization header keep your tokens server-side, letting team members browse brand assets in a Bubble repeating group without a Canva seat.

Quick facts about this guide
FactValue
ToolCanva
CategoryMedia & Content
MethodBubble API Connector
DifficultyAdvanced
Time required3–4 hours
Last updatedJuly 2026

Canva Connect API + Bubble: brand assets without extra seats

Canva's Connect API (available on Canva Teams and Enterprise) lets your Bubble app read a team's design library without requiring every team member to have a Canva login. The integration challenge is OAuth 2.0 with PKCE — Canva mandates the Proof Key for Code Exchange extension, which requires generating a code verifier and code challenge before the user is sent to the Canva authorization page. Bubble has no built-in OAuth consent handler, so you build it yourself: a Link element opens the Canva authorization URL, and a Backend Workflow at your redirect URI receives the authorization code, exchanges it for tokens, and saves them to the User's record. After that, every API Connector call to Canva endpoints uses the stored access token in a Private header, keeping it entirely server-side. Thumbnail URLs returned by the API are pre-signed and expire in minutes — so they must be read fresh from each API response and never cached in Bubble's database. A Scheduled Backend Workflow refreshes the token automatically every 55 minutes, keeping the integration running without any user action.

Integration method

Bubble API Connector

Uses Bubble's API Connector with a Private Authorization header to call Canva Connect API endpoints server-side, combined with Backend Workflows that handle OAuth 2.0 PKCE token exchange and refresh.

Prerequisites

  • A Canva Teams or Enterprise plan — the Connect API is not available on Canva Free or Pro plans; all API calls will return 401 without a Teams subscription
  • A Canva developer account and registered app at developers.canva.com — you will need the Client ID and Client Secret from the app settings
  • A Bubble paid plan — Backend Workflows (API Workflows) are not available on Bubble's free tier; the OAuth redirect handler and token refresh both require a paid plan
  • Familiarity with Bubble's Backend Workflows section and the API Connector plugin — this integration is rated Advanced

Step-by-step guide

1

Register your app at developers.canva.com and configure OAuth

Go to developers.canva.com and sign in with your Canva Teams account. Click 'Create an integration' and fill in your app name and description. Under 'OAuth 2.0 settings', add your redirect URI — this must point to a specific Bubble page you will create to receive the authorization code (for example, `https://yourapp.bubbleapps.io/oauth-callback`). Copy your Client ID and Client Secret to a safe place. In the 'Scopes' section, enable `design:content:read` and `asset:read` — these are the minimum permissions needed to browse designs and their thumbnails. Note that `design:content:write` is needed if you plan to create or duplicate designs. Back in Bubble, add two new fields to the User data type: `canva_access_token` (text), `canva_refresh_token` (text), and `canva_token_expiry` (date). These will store each user's OAuth credentials securely in your Bubble database.

Pro tip: Canva Teams is the minimum plan required — verify the subscription before building. A Free or Pro Canva account will return 401 on every Connect API call, and there is no workaround.

Expected result: You have a registered Canva app with Client ID, Client Secret, and a redirect URI pointing to your Bubble app. Your Bubble User data type has three new fields for storing OAuth tokens.

2

Build the OAuth consent link and PKCE code generation

PKCE (Proof Key for Code Exchange) is mandatory for Canva OAuth — a standard client_secret-only authorization will be rejected. You need to generate a `code_verifier` (a random string of 43–128 characters) and a `code_challenge` (the SHA-256 hash of the code_verifier, base64url-encoded) before sending the user to Canva. In Bubble, the cleanest way is to use the Toolbox plugin (available from the Bubble Plugin Marketplace): install it, then add a 'Run JavaScript' action in a Button click workflow. The JavaScript generates the code_verifier, stores it in an App State (so the Backend Workflow can retrieve it during callback), computes the SHA-256 hash using the Web Crypto API, and redirects the user to the Canva authorization URL. The authorization URL format is: `https://www.canva.com/api/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=design:content:read+asset:read&code_challenge=GENERATED_CHALLENGE&code_challenge_method=S256`. Store the code_verifier in a Bubble App State called `canva_code_verifier` so the token-exchange Backend Workflow can read it. If you prefer not to use JavaScript, a custom Bubble plugin can handle the crypto operations — RapidDev's team has built hundreds of Bubble apps with OAuth integrations like this; a free scoping call at rapidevelopers.com/contact can help if the PKCE step feels daunting.

canva_pkce_redirect.js
1// Toolbox plugin — Run JavaScript action
2// Generates PKCE code_verifier and code_challenge, redirects to Canva OAuth
3
4function base64urlEncode(buffer) {
5 return btoa(String.fromCharCode(...new Uint8Array(buffer)))
6 .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
7}
8
9async function startCanvaOAuth() {
10 // Generate code_verifier: 96 random bytes → base64url
11 const verifierBytes = crypto.getRandomValues(new Uint8Array(96));
12 const codeVerifier = base64urlEncode(verifierBytes);
13
14 // SHA-256 hash → base64url
15 const encoder = new TextEncoder();
16 const data = encoder.encode(codeVerifier);
17 const hashBuffer = await crypto.subtle.digest('SHA-256', data);
18 const codeChallenge = base64urlEncode(hashBuffer);
19
20 // Store verifier in sessionStorage for callback retrieval
21 sessionStorage.setItem('canva_code_verifier', codeVerifier);
22
23 const params = new URLSearchParams({
24 response_type: 'code',
25 client_id: 'YOUR_CLIENT_ID',
26 redirect_uri: 'https://yourapp.bubbleapps.io/oauth-callback',
27 scope: 'design:content:read asset:read',
28 code_challenge: codeChallenge,
29 code_challenge_method: 'S256'
30 });
31
32 window.location.href = 'https://www.canva.com/api/oauth/authorize?' + params.toString();
33}
34
35startCanvaOAuth();

Pro tip: The code_verifier must be stored somewhere the callback page can retrieve it. sessionStorage works for the same browser session; for more robustness, write it to a custom Bubble User field or App State before the redirect.

Expected result: Clicking your 'Connect Canva' button runs the JavaScript, generates PKCE parameters, and redirects the user to Canva's authorization page where they can grant access to your app.

3

Build the OAuth callback Backend Workflow to exchange code for tokens

Create a new Bubble page called `oauth-callback`. This is the page your users land on after approving access in Canva. In the page's 'Page is loaded' event, add a 'Run a Backend Workflow' action — this Backend Workflow will handle the token exchange. First, go to Bubble's Settings → API tab, enable 'This app exposes a Workflow API,' and then open the Backend Workflows editor. Create a new workflow called 'Canva Token Exchange.' This workflow receives the URL parameter `code` (which Canva appends automatically) and a `state` parameter if you used one. In the workflow, add an API call step: POST to `https://api.canva.com/rest/v1/oauth/token` with body type `application/x-www-form-urlencoded` and these fields: `grant_type=authorization_code`, `code=[the code parameter]`, `redirect_uri=[your callback URL]`, `client_id=[your Client ID]`, `client_secret=[your Client Secret]`, `code_verifier=[retrieved from App State or sessionStorage]`. The response contains `access_token`, `refresh_token`, and `expires_in` (seconds). Save all three to the current User's record: `canva_access_token`, `canva_refresh_token`, and calculate `canva_token_expiry` as current date/time + expires_in seconds. After saving, redirect the user to your main app page. Note: Backend Workflows require a Bubble paid plan.

canva_token_exchange.json
1{
2 "endpoint": "POST https://api.canva.com/rest/v1/oauth/token",
3 "headers": {
4 "Content-Type": "application/x-www-form-urlencoded"
5 },
6 "body": {
7 "grant_type": "authorization_code",
8 "code": "<dynamic: URL parameter 'code'>",
9 "redirect_uri": "https://yourapp.bubbleapps.io/oauth-callback",
10 "client_id": "<your_client_id>",
11 "client_secret": "<private>",
12 "code_verifier": "<dynamic: App State 'canva_code_verifier'>"
13 },
14 "response_fields": [
15 "access_token",
16 "refresh_token",
17 "expires_in",
18 "token_type"
19 ]
20}

Pro tip: After saving the tokens, always also calculate and save an expiry timestamp (current time + expires_in seconds). You will use this to check whether to refresh before each API call.

Expected result: After approving access on Canva, users are redirected to your callback page, the Backend Workflow fires, tokens are saved to their User record, and they are sent to your app's main page.

4

Add the Canva API Connector and create a 'Get Designs' call

Go to Plugins tab → Add plugins → search for 'API Connector' (by Bubble) and install it. Click 'API Connector' in your plugins list, then 'Add another API.' Name it 'Canva Connect.' Set the base URL to `https://api.canva.com/rest/v1`. Under 'Shared headers,' add one header: key `Authorization`, value `Bearer [Current User's canva_access_token]`. Check the 'Private' checkbox — this ensures the token travels server-to-server and never appears in browser requests. Now click 'Add a call' and configure it: Name it 'Get Designs,' set method to GET, and set the URL to `/designs`. Add URL parameters: `type` (optional, e.g., `presentation`), `query` (optional search string), `page_size` (set default `20`), and `continuation` (for pagination). Under 'Use as,' choose 'Data' — this lets you bind the response to a repeating group. Click 'Initialize call': Bubble needs a real successful response to auto-detect the field structure. Make sure your test user has a valid access token saved in their User record before clicking Initialize. If the call returns a valid response, Bubble will list the fields it found: `items[].id`, `items[].title`, `items[].thumbnail.url`, `items[].owner.name`, `items[].created_at`, `items[].updated_at`. Expand the `items` array in the response definition and mark it as the list type.

canva_api_connector.json
1{
2 "api_name": "Canva Connect",
3 "base_url": "https://api.canva.com/rest/v1",
4 "shared_headers": [
5 {
6 "key": "Authorization",
7 "value": "Bearer <dynamic: Current User's canva_access_token>",
8 "private": true
9 }
10 ],
11 "calls": [
12 {
13 "name": "Get Designs",
14 "method": "GET",
15 "path": "/designs",
16 "parameters": [
17 { "key": "type", "value": "<dynamic>", "optional": true },
18 { "key": "query", "value": "<dynamic>", "optional": true },
19 { "key": "page_size", "value": "20" },
20 { "key": "continuation", "value": "<dynamic>", "optional": true }
21 ],
22 "use_as": "Data"
23 }
24 ]
25}

Pro tip: The Initialize call requires a real, live access token and an active Canva Teams account. If you see 'There was an issue setting up your call,' your token may have expired or the Canva Teams plan may not be active. Refresh the token manually first.

Expected result: The API Connector shows a 'Get Designs' call with detected response fields including items[], thumbnail URLs, and design metadata. You can now use this call as a data source in Bubble.

5

Build the brand-asset library repeating group

Add a new Bubble page or section for your brand-asset library. Add a Repeating Group element and set its data source to 'Get Designs from Canva Connect — items' (the list returned by your API call). Set the layout to Grid or Extended Vertical List depending on your design preference. Inside the repeating group cell, add an Image element: set its dynamic source to 'Current cell's Canva Connect Get Designs items — thumbnail_url'. Canva thumbnail URLs are pre-signed and expire in approximately five minutes — never save them to Bubble's database. Always bind them directly from the current API response in real time. Add a Text element for the design title (dynamic: `items — title`). Add an Input and a Dropdown above the repeating group for search and type filtering — wire the Input's value to the `query` parameter and the Dropdown's value to the `type` parameter. Add a 'Load more' button wired to the `continuation` value from the last response to support pagination. On page load, trigger a workflow that first checks whether the user's `canva_token_expiry` is less than 5 minutes away — if so, call the token refresh Backend Workflow before running the Get Designs call.

Pro tip: Set the repeating group's 'No. rows' and 'No. columns' to match your page_size. If you request 20 designs per page, size the repeating group to show 20 cells before the 'Load more' button appears.

Expected result: The brand-asset library page loads Canva designs into a searchable, filterable repeating group with live thumbnail images. Founders can browse and filter team assets without logging into Canva.

6

Add the automatic token refresh Backend Workflow

Create a second Backend Workflow called 'Refresh Canva Token.' This workflow accepts one input: `user` (type User). It POSTs to `https://api.canva.com/rest/v1/oauth/token` with body type `application/x-www-form-urlencoded` and these fields: `grant_type=refresh_token`, `refresh_token=[input user's canva_refresh_token]`, `client_id=[your Client ID]`, `client_secret=[your Client Secret]`. The response returns a new `access_token`, `refresh_token`, and `expires_in`. After the API call, add a 'Make changes to a Thing' step: update the input user's `canva_access_token` with the new token, `canva_refresh_token` with the new refresh token, and `canva_token_expiry` with current date/time + expires_in seconds. Call this Backend Workflow from two places: on page load when expiry is within 5 minutes, and as a pre-step in any workflow that makes a Canva API call. This pattern ensures your tokens are always fresh without requiring users to re-authorize. The Canva access token expires in 1 hour — without this refresh step, your repeating group will silently stop loading after the first hour.

canva_token_refresh.json
1{
2 "endpoint": "POST https://api.canva.com/rest/v1/oauth/token",
3 "headers": {
4 "Content-Type": "application/x-www-form-urlencoded"
5 },
6 "body": {
7 "grant_type": "refresh_token",
8 "refresh_token": "<dynamic: input user's canva_refresh_token>",
9 "client_id": "<your_client_id>",
10 "client_secret": "<private>"
11 },
12 "response_fields": [
13 "access_token",
14 "refresh_token",
15 "expires_in"
16 ]
17}

Pro tip: Add a Data Privacy rule in Bubble's Data tab for the User type — restrict access to the `canva_access_token` and `canva_refresh_token` fields so only the user themselves (or your app's server-side workflows) can read them.

Expected result: The Refresh Canva Token Backend Workflow successfully exchanges an old refresh token for a new access token, updates the User record, and keeps the integration alive without user re-authorization.

Common use cases

Brand Asset Library for Team Members

Surface your Canva Teams design library in a Bubble app so non-designer team members can browse, preview, and copy links to approved brand assets without needing a Canva seat. Use the GET /designs endpoint filtered by type and tag to build a searchable gallery.

Bubble Prompt

Build a Bubble page that displays a searchable repeating group of Canva designs filtered by 'presentation' type, showing the thumbnail, title, and a copy-link button for each design.

Copy this prompt to try it in Bubble

Campaign Asset Approval Portal

Let marketing managers browse Canva designs from inside a Bubble workflow tool, mark assets as approved or rejected, and record decisions in Bubble's database — without switching to Canva. The API provides thumbnail previews; approval status lives in Bubble.

Bubble Prompt

Create a Bubble admin page where marketing managers can see all Canva designs, click a thumbnail to preview it, and click Approve or Reject to update the asset status in the Bubble database.

Copy this prompt to try it in Bubble

Client-Facing Design Delivery Portal

For agencies using Canva for client deliverables, build a Bubble client portal that displays only the designs tagged for that client. Clients log into Bubble, see their brand assets, and download links — without ever touching Canva.

Bubble Prompt

Build a Bubble client portal that shows Canva designs filtered by a client tag, displays thumbnails with expiry-safe URLs loaded fresh from the API on each page visit, and shows a download link for each design.

Copy this prompt to try it in Bubble

Troubleshooting

API Connector Initialize call returns 'There was an issue setting up your call' or a 401 error

Cause: The Canva access token used during initialization is expired, missing from the test user's record, or the Canva Teams plan is not active. A 401 is also returned if the app is being tested with a Canva Free or Pro account.

Solution: Verify the test user has a valid `canva_access_token` stored in their Bubble User record. Check the token expiry. Confirm the connected Canva account is on a Teams or Enterprise plan — log into canva.com and verify the plan under Account Settings. Re-run the OAuth flow for your test user before re-initializing the call.

PKCE authorization fails — Canva returns 'invalid_request' or 'code_challenge_mismatch'

Cause: The code_verifier stored at redirect time does not match the code_verifier sent in the token exchange request. This happens if the App State was cleared between the redirect and the callback, or if sessionStorage was not read correctly by the callback page.

Solution: Ensure the code_verifier is stored in a persistent location before the redirect — a Bubble custom User field (e.g., `canva_pkce_verifier`) is more reliable than sessionStorage across redirects. Read this field in the token-exchange Backend Workflow rather than relying on browser-side storage.

Thumbnail images in the repeating group break or show broken links after a few minutes

Cause: Canva thumbnail URLs are pre-signed and expire in approximately five minutes. If they are saved to Bubble's database (e.g., stored in a Canva Design data type), they will be stale by the time the page is loaded again.

Solution: Never save thumbnail URLs to Bubble's database. Always bind the Image element's source directly to the current API response field (Current cell's Get Designs items — thumbnail_url). The repeating group fetches fresh URLs on every page load, so they are always valid.

Backend Workflow 'Canva Token Exchange' or 'Refresh Canva Token' is not available in the workflow editor

Cause: Backend Workflows (API Workflows) require a Bubble paid plan. On the free plan, the Backend Workflows section does not appear and 'Run a Backend Workflow' is not available as a workflow action.

Solution: Upgrade to a Bubble Starter or Growth plan to unlock Backend Workflows. This integration cannot be completed on Bubble's free tier because both the OAuth callback handler and the token refresh workflow depend on Backend Workflows.

The 'Get Designs' call returns an empty list even though designs exist in the Canva account

Cause: The `type` or `query` filter parameters may be too restrictive, or the access token is associated with a user who does not have the `design:content:read` scope. The app may also not have been granted access to the correct Canva team.

Solution: Remove all optional filter parameters and call GET /designs with only `page_size=20` to test. In the Canva developer dashboard, verify the app is connected to the correct team and that the `design:content:read` scope was granted during the OAuth flow. If the user denied any scope during authorization, they need to revoke and re-authorize.

Best practices

  • Store `canva_access_token`, `canva_refresh_token`, and `canva_token_expiry` on the Bubble User record — never in App Data or a generic Settings table — so tokens are isolated per user and cannot leak across accounts.
  • Add a Bubble Data Privacy rule restricting the `canva_access_token` and `canva_refresh_token` fields to 'Current User' only — this prevents other logged-in users from reading another user's Canva tokens through the API.
  • Always check token expiry before making API calls. If `canva_token_expiry` is within 5 minutes, call the Refresh Token Backend Workflow first. This prevents intermittent 401 errors mid-session.
  • Never cache Canva thumbnail URLs in Bubble's database — they are pre-signed and expire in approximately five minutes. Bind thumbnails directly from the live API response each page load.
  • Use the `_fields` approach by relying on the `type` and `query` parameters to filter API responses rather than loading all designs and filtering client-side — Canva's API paginates results, and filtering server-side keeps WU consumption lower.
  • Monitor Bubble's Logs tab → Workflow logs after launch to track API call frequency and WU consumption. Canva API calls count against Bubble's WU budget, so avoid unnecessary polling.
  • Confirm the Canva Teams subscription is active before launching. If the subscription lapses, all API calls return 401 and the integration breaks silently — set up an internal alert or health-check workflow.

Alternatives

Frequently asked questions

Does the Canva Connect API work on the free Canva plan?

No. Canva's Connect API is restricted to Canva Teams and Enterprise plans. Attempting to use it with a Free or Pro account returns a 401 Unauthorized error on all Connect API calls. Verify your Canva plan at canva.com/pricing before starting the integration.

Why does Bubble require a paid plan for this integration?

The OAuth callback handler and token refresh mechanism both use Backend Workflows (also called API Workflows), which are only available on Bubble's paid plans. Without Backend Workflows, there is no server-side way to receive the authorization code from Canva's redirect or to automatically refresh tokens.

Can I use Canva OAuth without PKCE?

No. Canva's OAuth 2.0 implementation mandates PKCE (Proof Key for Code Exchange). Sending an authorization request without a code_challenge and code_challenge_method=S256 will return an error. You must generate a code_verifier and code_challenge before redirecting users to the Canva authorization page.

How long do Canva access tokens last?

Canva access tokens expire in 1 hour (3,600 seconds). After that, any API call using the expired token returns 401. Use the refresh_token to obtain a new access_token before expiry — the Bubble Scheduled Backend Workflow approach running every 55 minutes is the cleanest automated solution.

Can I read, edit, or create Canva designs from Bubble?

The Connect API's public scope `design:content:read` lets you list and read designs. Write operations (creating or editing designs) require additional scopes and are available on higher-tier Connect API access. Check developers.canva.com for current scope availability — read-only access is the most commonly approved tier.

Why do thumbnails stop loading after a few minutes?

Canva thumbnail URLs are pre-signed temporary URLs that expire in approximately five minutes. If you save them to Bubble's database, they will be broken when loaded later. Always bind Image elements directly to the current API response — never store the URLs as data.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Bubble integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.