Zeplin is a design handoff and specification tool with a REST API at api.zeplin.dev/v1. Connect Bubble to Zeplin using the API Connector to display screen thumbnails, project lists, and component specs inside a client-approval portal — letting clients browse, comment on, and approve designs in a branded Bubble app without needing a Zeplin account.
| Fact | Value |
|---|---|
| Tool | Zeplin |
| Category | DevOps & Tools |
| Method | Bubble API Connector |
| Difficulty | Beginner |
| Time required | 30–45 minutes |
| Last updated | July 2026 |
Build a client-approval portal connecting Bubble and Zeplin
The most common reason Bubble founders search for a Zeplin integration is the client review problem: design teams upload their screens to Zeplin, but sharing a Zeplin project link requires clients to create a Zeplin account, learn Zeplin's UI, and navigate a tool that was designed for developers — not decision-makers.
With Bubble and the Zeplin REST API, you can solve this by building a lightweight client portal in Bubble: clients see a gallery of screen thumbnails pulled from Zeplin, click through to a screen detail view, and leave approval comments that are stored in Bubble's database. No Zeplin account needed for clients. Your branding. Your workflow.
The Zeplin API (`https://api.zeplin.dev/v1`) supports reading: - **Projects** — name, description, platform (iOS/Android/Web) - **Screens** — name, thumbnail image URL, creation date - **Components** — design system components with measurement data - **Assets** — downloadable asset URLs
Authentication uses a Personal Access Token generated in your Zeplin profile — simpler than OAuth 2.0 and sufficient for single-account integrations. For multi-account apps (e.g., an agency serving many clients), Zeplin also supports OAuth 2.0, but that requires a paid Bubble plan to handle the OAuth redirect via a Backend Workflow.
Zeplin's free plan limits you to 1 project and 5 screens via API — confirm your plan tier before building if you need access to multiple projects.
Integration method
Call the Zeplin REST API from Bubble's server-side API Connector using a Private Personal Access Token to read projects, screens, and component specs.
Prerequisites
- A Bubble account (any plan works for outbound API calls; paid plan required for receiving inbound Zeplin webhook events)
- A Zeplin account at app.zeplin.io with at least one project containing screens
- A Zeplin plan with API access — the Free plan allows 1 project and 5 screens; Starter or higher is recommended for real projects
- A Zeplin Personal Access Token (generated in the next step)
- The 'API Connector' plugin by Bubble installed in your app (Plugins tab → Add plugins → search 'API Connector')
Step-by-step guide
Step 1 — Generate a Zeplin Personal Access Token
Open your browser and go to `app.zeplin.io`. Log in with your Zeplin account. Click your avatar or profile picture in the top-right corner to open the profile menu, then click **Profile**. On the Profile page, scroll down to find the **Developer** section (or click the **Developer** tab in the left sidebar if your Zeplin version shows one). Click **Personal Access Tokens** → **Create new token**. Give the token a name like `Bubble Integration` so you can identify it later. Zeplin does not let you choose specific scopes for Personal Access Tokens — the token grants read access to all projects and organisations your account has access to. Click **Create**. Zeplin shows you the token value once and only once — copy it immediately and store it in a password manager or a secure note. You cannot view this value again after leaving the page; if you lose it, you will need to delete the token and create a new one. Important security note: a Zeplin Personal Access Token does not expire, but it grants access to ALL projects on your account. For a production Bubble integration, use a dedicated Zeplin service account (a separate Zeplin user account with only the necessary projects shared to it) rather than your main designer account. This limits exposure if the token is ever compromised.
Pro tip: If you are an agency connecting multiple clients' Zeplin workspaces to a single Bubble app, consider creating a Zeplin service account per client and generating one token per client. This way, each client's screens are isolated and you can revoke one client's access without affecting others.
Expected result: You have a Zeplin Personal Access Token copied and stored securely. You can see it listed in the Developer → Personal Access Tokens section with a 'Last used: never' status.
Step 2 — Install the API Connector and create the Zeplin API group
In your Bubble app editor, click **Plugins** in the left sidebar. If the API Connector plugin (by Bubble) is not already installed, click **Add plugins** → search for **API Connector** → click **Install**. The plugin is free. Click the API Connector row to open its settings. Click **Add another API**. In the **API Name** field, type **Zeplin**. In the **Base URL** field, enter: `https://api.zeplin.dev/v1` Now add the authentication header. Click **Add a shared header**: - Key: `Authorization` - Value: `Bearer ` followed by the Personal Access Token you copied in Step 1 (format: `Bearer zeplin_at_...`) - Tick the **Private** checkbox immediately — this is the most important action in this step. The Private flag prevents your Zeplin token from being included in any data sent to the browser. Without it, a technically savvy user could extract the token from a network request and access your entire Zeplin account. Double-check: the Authorization header value starts with `Bearer ` (with a space), followed by the token. The Private checkbox is ticked. The base URL ends in `/v1` with no trailing slash. Your Zeplin API group is now configured. All calls you add to this group will automatically include the Authorization header.
1{2 "api_group": "Zeplin",3 "base_url": "https://api.zeplin.dev/v1",4 "shared_headers": [5 {6 "key": "Authorization",7 "value": "Bearer <private>",8 "private": true9 }10 ]11}Pro tip: To verify your token is correct before adding any calls, use the Zeplin API's identity endpoint: add a GET call at path `/users/me` and click Initialize. If it returns your Zeplin profile data, the token and base URL are both correct.
Expected result: The Zeplin API group is created in the API Connector with the base URL and a Private Authorization header. No calls are added yet.
Step 3 — Add calls to fetch projects and screens
Inside the Zeplin API group, click **Add a call** to create your first endpoint call. **Call 1 — List Projects:** Set the method to **GET** and the path to `/projects`. Set **Use as** to **Data (list)**. Click **Initialize call** — Bubble sends the GET request to `https://api.zeplin.dev/v1/projects` and reads the response. You will see a JSON array where each item has fields like `id`, `name`, `description`, `platform`, `thumbnail`. Bubble maps these automatically. If your Zeplin free plan only has 1 project, you will see a single-item array — that is fine for initialization. **Call 2 — List Screens for a Project:** Click **Add a call** again. Set the method to **GET**. In the path field, enter `/projects/[projectId]/screens`. Click the `[projectId]` portion — Bubble turns it into a parameter field. Name the parameter `projectId`. Set **Use as** to **Data (list)**. For initialization, you need a real project ID: copy one from the response you got in Call 1 (find the `id` field of a project). Paste it as the test value for `projectId`. Click **Initialize call**. The response will include screen objects with fields like `id`, `name`, `image` (containing a `thumbnails` sub-object with `small` and `large` image URLs), and `created`. Bubble maps all of these. The thumbnail URLs point to Zeplin's CDN — they are publicly accessible HTTPS image URLs that Bubble's Image elements can load directly without any proxying. **Call 3 — Verify token (optional):** Add a GET call at path `/users/me` (Use as: Data). Initialize it. If it returns your profile, your token is valid. You can use this call to show 'Connected as: [Zeplin username]' in your Bubble app's settings panel.
1{2 "calls": [3 {4 "name": "List Projects",5 "method": "GET",6 "path": "/projects",7 "use_as": "Data (list)",8 "example_response_item": {9 "id": "5f8a3c2e1a2b3c4d5e6f7g8h",10 "name": "Mobile App v2",11 "description": "Redesign 2026",12 "platform": "ios",13 "thumbnail": "https://img.zeplin.io/..."14 }15 },16 {17 "name": "List Screens",18 "method": "GET",19 "path": "/projects/{projectId}/screens",20 "parameters": [21 { "key": "projectId", "value": "<your-project-id>" }22 ],23 "use_as": "Data (list)",24 "example_response_item": {25 "id": "screen_abc123",26 "name": "Home Screen",27 "image": {28 "thumbnails": {29 "small": "https://img.zeplin.io/thumbnail_small.png",30 "large": "https://img.zeplin.io/thumbnail_large.png"31 }32 },33 "created": 174800000034 }35 }36 ]37}Pro tip: If Initialize call returns an empty array `[]` for the screens call even though you can see screens in Zeplin, confirm that the `projectId` you used is the correct ID (not the project name). Copy the `id` field from the List Projects response — it is a long alphanumeric string, not the human-readable project name.
Expected result: Both calls show 'Initialized' in the API Connector. The List Projects call shows at least one project in the response preview. The List Screens call shows screen objects with `name` and `image - thumbnails - small` fields mapped.
Step 4 — Build a project gallery page with screen thumbnails
Now wire the Zeplin API data to your Bubble UI. You will build a two-level browsing experience: a project list page and a screen gallery that appears when a project is selected. In your Bubble editor, create a new page called `design-portal`. From the element toolbar, drag in a **Repeating Group**. Click it to open properties. Set **Type of content** to **Zeplin List Projects** (Bubble auto-created this type from the initialization). Set **Data source** to **Get data from an external API → Zeplin - List Projects**. No parameters needed. Inside the Repeating Group's first cell, add: - A **Text** element bound to `Current cell's Zeplin List Projects → name` - An **Image** element bound to `Current cell's Zeplin List Projects → thumbnail` - A **Button** labelled 'View Screens' Now create a second Repeating Group on the same page (or a separate screen). Set its **Type of content** to **Zeplin List Screens** and its **Data source** to **Get data from an external API → Zeplin - List Screens**, with `projectId` set to a page URL parameter or a custom state that stores the selected project ID. Inside the Screens Repeating Group cell, add: - An **Image** element bound to `Current cell's Zeplin List Screens → image - thumbnails - small` - A **Text** element bound to `Current cell's Zeplin List Screens → name` For the button 'View Screens' on the project row: add a workflow — **Element Actions → Set state → page's custom state 'selected_project_id' = Current cell's project id**. Then show the screens Repeating Group (which reads from that custom state as its projectId parameter) and hide the projects Repeating Group. Preview the page. You should see your Zeplin projects listed with thumbnails. Clicking 'View Screens' on any project shows that project's screens as a thumbnail gallery.
Pro tip: Zeplin screen thumbnail URLs expire after a period of time — they are signed CDN URLs. If you store them in Bubble's database for caching, they may break after a few hours. For the client portal use-case, fetch them fresh from the API on each page load rather than caching the image URLs long-term.
Expected result: The Bubble page shows a gallery of Zeplin projects. Clicking a project reveals a grid of screen thumbnails with their names — all pulled live from the Zeplin API.
Step 5 — Add a client feedback form and store approvals in Bubble's database
The client portal is more valuable when clients can take action on what they see. This step adds a feedback form to each screen thumbnail so clients can leave comments and mark screens as approved — all stored in Bubble's database. First, create a new Bubble data type called **ScreenFeedback** with these fields: - `screen_id` (text) — Zeplin's screen ID - `screen_name` (text) — the screen's name for display - `project_id` (text) — the Zeplin project ID - `client_name` (text) — who left the feedback - `comment` (text) — the feedback text - `status` (option set: Pending / Approved / Needs Revision) - `created_date` (date) Go to **Data → Privacy** and add a privacy rule for ScreenFeedback: set it so that only logged-in users can see their own organisation's feedback records (or configure as appropriate for your app's auth setup). Without a privacy rule, all records are visible to all Bubble users by default. Back on the design-portal page, inside the Screens Repeating Group cell, add a **Popup** element or an expandable group that appears on screen click. Inside it, add: - A **Text** element: `Current cell's Zeplin List Screens → name` - An **Image** element: `Current cell's Zeplin List Screens → image - thumbnails - large` - A **Text Input** element for client comments - A **Dropdown** element with options Pending / Approved / Needs Revision - A **Button** labelled 'Submit Feedback' For the Submit Feedback button's workflow: 1. Step 1 → **Data (Things) → Create a new ScreenFeedback** with: - `screen_id` = Current popup's screen ID custom state - `screen_name` = current screen name - `project_id` = page's selected_project_id custom state - `comment` = Input's value - `status` = Dropdown's value - `created_date` = Current date/time 2. Step 2 → **Element actions → Reset relevant inputs** (clear the form) 3. Step 3 → **Element actions → Hide popup** RapidDev's team has built client-portal workflows exactly like this for design agencies — if you need help wiring up email notifications when a client leaves feedback, or building a consolidated approval status view for the design team, book a free scoping call at rapidevelopers.com/contact.
Pro tip: Add a Bubble Privacy rule to the ScreenFeedback data type before testing with real clients. Without it, any logged-in user can read all feedback records through Bubble's default data API — a significant data exposure risk in a multi-client portal.
Expected result: Clients can click any screen thumbnail, see a larger image, type feedback, select Approved / Needs Revision, and submit. The feedback appears in Bubble's database under the ScreenFeedback data type.
Step 6 — Display existing feedback per screen and set up a summary view for designers
Complete the portal by showing each screen's current approval status alongside its thumbnail and building a designer summary view showing overall handoff progress. In the Screens Repeating Group cell (alongside the thumbnail and screen name), add a text element for status. Set its data source to: `Search for ScreenFeedback items` where `screen_id = Current cell's Zeplin List Screens → id`, sorted by `created_date descending`, with `:first item`'s `status`. Add Conditional formatting: when status is 'Approved', show green text; when 'Needs Revision', show red; when 'Pending' or empty, show gray. For the designer summary view, create a new page `design-summary` (visible only to team members, not clients). On this page: - Add a text element: `Search for ScreenFeedback items where status = Approved → count` + ' screens approved' - Add another: `Search for ScreenFeedback items where status = Needs Revision → count` + ' screens need revision' - Add a Repeating Group of all ScreenFeedback items where status = 'Needs Revision', showing screen_name, comment, client_name, and created_date This gives the design team a single page showing which screens need rework and what clients said — without any manual Zeplin account management. Optional: add an email notification workflow. After the ScreenFeedback Create step, add a Sendgrid or built-in email action to notify the design team's email address when a client marks a screen as 'Needs Revision'. This closes the feedback loop automatically.
Pro tip: Use Bubble's 'Do a search for' with 'Count' for the summary numbers rather than fetching all records and counting in the UI — Bubble's server-side count query is significantly more efficient and uses fewer WU than loading a full list just to display a number.
Expected result: Each screen thumbnail in the gallery now shows its current approval status (colour-coded). The design-summary page shows aggregate counts and a list of screens flagged for revision — giving the design team full visibility into client feedback.
Common use cases
Client design-approval portal
Pull Zeplin screen thumbnails into a branded Bubble app where clients can browse all project screens, click to see a larger view, write approval notes, and mark screens as 'Approved' or 'Needs Revision' — with all feedback stored in Bubble's database and emailed to the design team.
How do I build a Bubble page where clients can see all screens from my Zeplin project, click each one to leave a comment, and mark it as approved?
Copy this prompt to try it in Bubble
Design system documentation viewer
Fetch Zeplin component data (colour values, typography specs, spacing) via the API and display it in a Bubble-built design system documentation page that non-technical team members can access without opening Zeplin.
How can I display the color palette and text styles from a Zeplin project in a Bubble page as a visual style guide?
Copy this prompt to try it in Bubble
Design handoff progress tracker
Compare Zeplin screen count against a Bubble database of 'implemented' screens to build a handoff progress dashboard — showing what percentage of designs have been coded, which screens are pending, and which have been approved by the client.
I want a Bubble dashboard that shows how many Zeplin screens are in each status: waiting for approval, approved, and implemented. How do I set that up?
Copy this prompt to try it in Bubble
Troubleshooting
Initialize call returns 'There was an issue setting up your call' with a 401 status
Cause: The Zeplin Personal Access Token is incorrect, missing, or was pasted with extra whitespace. A 401 from Zeplin's API always means the Authorization header value is not recognised.
Solution: Open the API Connector, click the Zeplin group, and click the Authorization shared header. Confirm the value is exactly `Bearer ` (with a space after Bearer) followed by your token string. Do not include newlines or extra spaces. If you are unsure whether the token was copied correctly, go to app.zeplin.io → Profile → Developer → Personal Access Tokens → delete the old token → create a new one → copy it fresh.
List Screens call returns an empty array even though screens exist in Zeplin
Cause: The `projectId` parameter value is either empty, using the project name instead of the project ID, or the project is on Zeplin's Free plan (limited to 1 project and 5 screens). If you have more than 5 screens, the API may return only the first 5.
Solution: First, run the List Projects call in Initialize mode and look at the response — find the `id` field (a long alphanumeric string like `5f8a3c2e...`). Copy that exact ID and use it as the `projectId` parameter for the List Screens call. Do not use the project name. If you only see 5 screens and Zeplin shows more, upgrade your Zeplin plan — the free tier restricts API access to 5 screens per project.
Screen thumbnails show broken image icons in Bubble instead of loading
Cause: Zeplin's thumbnail URLs are signed CDN links that expire after a period. If you cached the image URL in a Bubble database field hours ago, the URL may no longer be valid.
Solution: For the client portal use-case, do not cache Zeplin thumbnail URLs in Bubble's database long-term. Instead, fetch them fresh from the API on each page load using the List Screens call as the Repeating Group's data source. If you must cache for performance, treat the cache as valid for no more than 30 minutes and re-fetch after that threshold.
Backend Workflow does not receive Zeplin webhook events for screen additions or project updates
Cause: Backend Workflows (API Workflows) require a paid Bubble plan. On the free plan, Bubble does not expose a public webhook endpoint. Additionally, Zeplin webhooks (screen_added, comment_added, project_updated) are only available on Zeplin's Organization plan or higher — they are not available on Free, Starter, or Growing plans.
Solution: To receive Zeplin webhooks in Bubble, you need both a paid Bubble plan (to enable Backend Workflows under Settings → API → 'This app exposes a Workflow API') and a Zeplin Organization plan. Once both are active, create a Bubble Backend Workflow → copy its public URL (format: `https://yourapp.bubbleapps.io/api/1.1/wf/{workflow-name}`) → register it in Zeplin workspace Settings → Webhooks.
Zeplin share links (zeplin.live/...) do not load when placed in a Bubble HTML element as an iframe
Cause: Zeplin's share links use X-Frame-Options or Content-Security-Policy headers that prevent embedding in iframes. This behaviour is intentional and consistent across browsers.
Solution: Do not attempt to embed Zeplin share links in Bubble's HTML element as iframes — they will not load. Instead, use the Zeplin API to fetch thumbnail image URLs and display them in Bubble's native Image elements. For the full Zeplin view, add a 'Open in Zeplin' link button that opens the share URL in a new browser tab.
Best practices
- Always tick the 'Private' checkbox on the Authorization header in the API Connector. Your Zeplin Personal Access Token grants access to all projects on your Zeplin account — a leak is far more damaging than a narrowly-scoped key.
- Use a dedicated Zeplin service account for your Bubble integration rather than your main designer account. Create a separate Zeplin user with only the necessary projects shared to it, generate the token from that account, and use it in Bubble. This limits exposure if the token is ever compromised and prevents the integration from breaking if a designer leaves the organisation.
- Do not cache Zeplin screen thumbnail URLs in Bubble's database for more than 30 minutes. Zeplin CDN links are signed and expire — treat them as ephemeral and fetch them fresh from the API on each page load for a portal with live accuracy.
- Add Privacy rules to any Bubble data types that store client feedback (ScreenFeedback) before going live. Without Privacy rules, Bubble's default behaviour exposes all records to all logged-in users via Bubble's data API — a significant risk in a multi-client design portal.
- Verify your Zeplin plan tier before building. The Free plan restricts the API to 1 project and 5 screens — you will see empty or truncated results if your project exceeds these limits. Check plan details at app.zeplin.io before committing to the integration.
- For the client-approval portal, show the latest feedback status per screen using a database search filtered by screen_id and sorted by created_date descending with :first item. This avoids loading all feedback records and displaying only the most recent status efficiently.
- Use Bubble's 'Do a search for → Count' for summary statistics (number of approved screens, number needing revision) rather than loading full record lists in a Repeating Group just to display a count. Count queries are processed server-side and use far fewer WU.
- If you need real-time notifications when Zeplin screens change, you will need both a paid Bubble plan (for Backend Workflows) and a Zeplin Organization plan (for webhooks). For lower-tier plans, a polling alternative is a Backend Workflow on a schedule that checks for new screens every few minutes — but factor in the WU cost of polling at scale.
Alternatives
Figma is the dominant design tool and has its own REST API for reading frames, components, and exported assets. If your design team uses Figma (more common than Zeplin for new projects), the Figma API offers richer data including vector content and design tokens. Zeplin is a handoff layer on top of design tools; Figma is the source of truth.
Sketch Cloud has its own REST API for reading documents, artboard previews, and share data. If your team uses Sketch as the primary design tool (Mac-only), the Sketch API provides similar capabilities to Zeplin for displaying artboard previews in a Bubble client portal — without needing a separate handoff tool.
Adobe XD was discontinued as a standalone product in 2023 and is now bundled with Creative Cloud. Its API requires Adobe IMS OAuth 2.0 (more complex than Zeplin's token auth). If your team still uses XD, the integration works but requires a paid Bubble plan for the OAuth redirect flow. For new projects, Figma is the recommended alternative to XD.
Frequently asked questions
Do I need a paid Bubble plan to connect to Zeplin?
No — outbound API Connector calls to the Zeplin REST API work on Bubble's free plan. You can fetch projects, screens, and components and display them in Repeating Groups without upgrading. A paid Bubble plan is only required if you want to receive inbound Zeplin webhook events (e.g., trigger a Bubble workflow when a screen is added). Zeplin webhooks also require a Zeplin Organization plan or higher, so both upgrades are needed for the webhook path.
Can clients see my Zeplin designs in Bubble without having a Zeplin account?
Yes — that is the main value of this integration. The Bubble app fetches screen thumbnails and project data from Zeplin via the API using your token, then displays them in a branded Bubble UI. Clients see only what your Bubble app shows them and interact with a form you control. They never touch Zeplin directly. You control the access — Bubble's authentication system (email/password or social login) gates who can see the portal.
Why can't I embed Zeplin share links in a Bubble HTML element?
Zeplin blocks iframe embedding on its share links using X-Frame-Options or Content-Security-Policy HTTP headers. This is a deliberate security decision by Zeplin and cannot be bypassed from your Bubble app. Use the Zeplin API to fetch thumbnail image URLs and display them natively in Bubble Image elements instead. For clients who need to see the full Zeplin spec (measurements, component details), add a 'View in Zeplin' link button that opens the share URL in a new browser tab.
How many projects and screens can I access via the Zeplin API?
It depends on your Zeplin plan. The Free plan limits you to 1 project and 5 screens visible via the API. The Starter plan ($8/month) allows 2 projects, Growing ($19/month) allows 5 projects, and Organization ($35/editor/month) allows unlimited projects with webhook access. If your API calls return empty arrays or truncated lists, check your Zeplin plan tier first.
Is it safe to use a Personal Access Token for a Bubble app used by many clients?
It is safe as long as the token is in a Private header and you use a dedicated Zeplin service account. Personal Access Tokens in Bubble's Private headers are never exposed to browser traffic. For better security in a multi-client agency scenario, create a separate Zeplin account (a service account) that is shared on only the client projects you need, and generate the token from that account. This way, if you need to revoke access for one client, you can remove them from the service account's project without affecting other integrations.
Can I display design measurements and component specs from Zeplin in Bubble?
Yes, via the Zeplin API's `/projects/{projectId}/components` endpoint, which returns design components with their measurement data. However, the full developer-facing spec view (exact pixel values, CSS properties, distance annotations) is what Zeplin renders natively — the API returns the raw data values, and you would need to build the UI to display them in Bubble. For most client-portal use-cases, screen thumbnails and approval status are sufficient without needing to replicate Zeplin's full spec view.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation