# YouTube API

- Tool: Bubble
- Difficulty: Beginner
- Time required: 1 hour
- Last updated: July 2026

## TL;DR

Connect Bubble to YouTube Data API v3 using a Google Cloud API key as a Private URL parameter — no OAuth, no token expiry for public video data. Use GET /channels to find the uploads playlist ID, then GET /playlistItems (1 quota unit per call) instead of /search (100 units per call) to list a channel's videos. Embed playback via an HTML element iframe and display view counts from GET /videos alongside it.

## YouTube API + Bubble: video galleries and channel showcases without draining your daily quota

YouTube is the world's second-largest search engine, and embedding a channel's videos in a Bubble app is one of the most common media integration requests. The YouTube Data API v3 makes this straightforward for public data — you need only a Google Cloud API key, no OAuth, no token expiry. But the API has a subtle trap that trips up most first-time integrators: the /search endpoint costs 100 quota units per call, and the free tier gives only 10,000 units per day. A busy Bubble app that uses /search to browse a channel's videos will exhaust its daily quota in 100 page loads. The correct pattern: use GET /channels once to retrieve the channel's uploads playlist ID (a permanent identifier like `UUxxxxxxxxxxxxxxxxxxxxxxxxx`), then use GET /playlistItems with that playlist ID to list videos — at just 1 quota unit per call. For video playback in Bubble, the API gives you the video ID, and you embed it via an HTML element with a YouTube iframe. A separate GET /videos call fetches the view count, like count, and full description to display alongside the embed. This tutorial covers all three patterns: channel browsing, video statistics, and iframe embedding.

## Before you start

- A Google account and access to Google Cloud Console at console.cloud.google.com — creating a project and API key is free
- Your YouTube Channel ID — find it in YouTube Studio → Settings → Channel → Advanced Settings (it starts with 'UC')
- A Bubble account (any plan, including free) — the read integration works without Backend Workflows
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → API Connector by Bubble)

## Step-by-step guide

### 1. Create a Google Cloud project, enable YouTube Data API v3, and generate a restricted API key

Go to console.cloud.google.com and sign in with your Google account. Click the project selector in the top navigation bar and choose 'New Project.' Give it a name like 'Bubble YouTube App' and click Create. Once the project is created, go to APIs & Services → Library. Search for 'YouTube Data API v3' and click Enable. Now create your API key: go to APIs & Services → Credentials → Create Credentials → API key. Google generates a key immediately — copy it. Before saving, click 'Restrict key' to apply a restriction: under 'API restrictions,' select 'Restrict key' and choose 'YouTube Data API v3' from the dropdown. Click Save. This ensures the key can only be used for YouTube API calls even if it is ever exposed. Back in the Credentials view, copy the API key. Also set up quota alerts: go to APIs & Services → Quotas and search for 'YouTube Data API v3.' Click the queries-per-day quota, then the alert bell icon, and set a warning at 80% (8,000 units). This prevents surprise quota exhaustion.

**Expected result:** You have a Google Cloud project with YouTube Data API v3 enabled, an API key restricted to that API, quota alerts configured, and your YouTube Channel ID (starts with 'UC') noted.

### 2. Add the YouTube API Connector in Bubble with a Private key parameter

In your Bubble editor, go to Plugins tab → Add plugins → search 'API Connector' → install the one by Bubble. Click 'API Connector' in your plugins list → 'Add another API.' Name it 'YouTube.' Set the base URL to `https://www.googleapis.com/youtube/v3`. Under 'Shared parameters' (URL parameters), click 'Add a parameter.' Set key to `key` and value to your Google Cloud API key. Check the 'Private' checkbox — this is critical. The API key is a permanent credential (no expiry) but must not appear in browser network requests where it could be harvested from DevTools. With Private checked, Bubble appends it server-side to every call. This is the simplest form of secure credential management — no OAuth flow, no token refresh, no expiry management. Save the API Connector configuration.

```
{
  "api_name": "YouTube",
  "base_url": "https://www.googleapis.com/youtube/v3",
  "shared_parameters": [
    {
      "key": "key",
      "value": "<your_google_cloud_api_key>",
      "private": true
    }
  ]
}
```

**Expected result:** The YouTube API entry appears in Bubble's API Connector with the Private 'key' parameter configured as a shared parameter. All calls you add will automatically include the API key.

### 3. Create a GET /channels call to retrieve the uploads playlist ID

Every YouTube channel has a hidden uploads playlist that contains all of its public videos. This playlist ID (format: `UUxxxxxxxxxxxxxxxxxxxxxxxxx` — note the 'UU' prefix replacing 'UC' from the channel ID) is what you will use to list videos efficiently at 1 quota unit per call. Click 'Add a call' inside the YouTube API Connector entry. Name it 'Get Channel.' Method: GET. Path: `/channels`. Add URL parameters: `id` (value: your YouTube Channel ID, beginning with 'UC'), `part` (value: `contentDetails`). Set 'Use as' to 'Data.' Click 'Initialize call' — Bubble sends the request and auto-detects the response. The key field is `items 0 contentDetails relatedPlaylists uploads` — this is the uploads playlist ID. Store this value in a Bubble App Data field or Custom State the first time your app loads — it never changes for a given channel, so you only need to fetch it once. Note: `videoId` in playlist item responses is at a different path than in video search results — define your response schema carefully so Bubble maps the field correctly.

```
{
  "call_name": "Get Channel",
  "method": "GET",
  "path": "/channels",
  "parameters": [
    { "key": "id",   "value": "<dynamic: your channel ID>" },
    { "key": "part", "value": "contentDetails" }
  ],
  "use_as": "Data",
  "key_response_path": "items[0].contentDetails.relatedPlaylists.uploads"
}
```

**Expected result:** The Get Channel call is initialized and Bubble shows the detected response including the uploads playlist ID at 'items contentDetails relatedPlaylists uploads'. This ID is the input to your /playlistItems call.

### 4. Create a GET /playlistItems call for quota-efficient video listing

Click 'Add a call' inside the YouTube API Connector. Name it 'Get Playlist Videos.' Method: GET. Path: `/playlistItems`. Add URL parameters: `playlistId` (dynamic — will receive the uploads playlist ID), `part` (value: `snippet,contentDetails`), `maxResults` (value: `50`), `pageToken` (dynamic, optional — for pagination). Set 'Use as' to 'Data.' Click 'Initialize call' — enter your uploads playlist ID in the `playlistId` field. Bubble detects the response: `items[]` array with per-video fields including `snippet title`, `snippet description`, `snippet publishedAt`, `snippet thumbnails medium url`, and `contentDetails videoId`. This last field — `contentDetails.videoId` — is the video ID you will use for embedding and for the GET /videos stats call. IMPORTANT: this path differs from /search results where videoId is at the root `id` field. This difference trips up many Bubble builders when they try to reuse a response schema — set up the response definition carefully to point to `contentDetails.videoId`. The /playlistItems call costs 1 quota unit per call, compared to 100 for /search.

```
{
  "call_name": "Get Playlist Videos",
  "method": "GET",
  "path": "/playlistItems",
  "parameters": [
    { "key": "playlistId", "value": "<dynamic: uploads playlist ID>" },
    { "key": "part",       "value": "snippet,contentDetails" },
    { "key": "maxResults", "value": "50" },
    { "key": "pageToken",  "value": "<dynamic>", "optional": true }
  ],
  "use_as": "Data",
  "key_fields": [
    "items[].snippet.title",
    "items[].snippet.publishedAt",
    "items[].snippet.thumbnails.medium.url",
    "items[].contentDetails.videoId"
  ],
  "quota_cost": "1 unit per call"
}
```

**Expected result:** The Get Playlist Videos call is initialized showing items[] with snippet.title, thumbnails, and contentDetails.videoId. The API response uses only 1 quota unit and you can confirm this in the Logs tab.

### 5. Build the video gallery repeating group with iframe embed

Add a Repeating Group to your Bubble page. Set its data source to 'Get Playlist Videos from YouTube — items.' Inside each cell, add an Image element bound to `snippet thumbnails medium url` for the thumbnail. Add Text elements for `snippet title` and `snippet publishedAt` (formatted as a date). Add a Custom State of type text named `selected_video_id` on the page. Wire a Click workflow on each repeating group cell to set `selected_video_id` to `Current cell's Get Playlist Videos items — contentDetails videoId`. Below the repeating group (or in a popup), add an HTML element for the video player. Set its HTML content dynamically: the static HTML wraps an iframe where the src includes the Custom State value. The iframe src format is: `https://www.youtube.com/embed/{videoId}`. In Bubble's HTML element dynamic content, concatenate the static iframe opening tag with the Custom State value and the closing tag. Set `frameborder=0` and `allowfullscreen` on the iframe. RapidDev's team has built YouTube-powered Bubble course platforms and channel showcases — for advanced features like playback tracking or chapter markers, reach out at rapidevelopers.com/contact for a free scoping call.

```
<!-- Bubble HTML element — dynamic content -->
<iframe
  src="https://www.youtube.com/embed/BUBBLE_DYNAMIC_VIDEOID"
  width="640"
  height="360"
  frameborder="0"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  allowfullscreen
></iframe>

<!-- Replace BUBBLE_DYNAMIC_VIDEOID with Bubble's dynamic expression:
     Page's selected_video_id (Custom State) -->
```

**Expected result:** The video gallery displays YouTube thumbnails and titles in a repeating group. Clicking any video updates the Custom State and the HTML element renders a YouTube player for the selected video.

### 6. Add a GET /videos call for view counts and stats, then test quota usage

To display view counts, like counts, and full video descriptions alongside the player, add a final API Connector call. Click 'Add a call,' name it 'Get Video Stats.' Method: GET. Path: `/videos`. Parameters: `id` (dynamic — the selected video ID or a comma-separated list of IDs), `part` (value: `statistics,snippet`). Set 'Use as' to 'Data.' Initialize with a real video ID. Response fields: `items 0 statistics viewCount`, `items 0 statistics likeCount`, `items 0 statistics commentCount`, `items 0 snippet description`, `items 0 snippet publishedAt`. Note that `subscriberCount` for channels that opt out privacy settings returns '0' — show '—' rather than '0' in the UI. Wire this call to a workflow triggered when `selected_video_id` changes: run Get Video Stats with the current Custom State value and display the results in Text elements next to the player. Finally, monitor quota usage: go to Google Cloud Console → APIs & Services → YouTube Data API v3 → Metrics. Verify your usage pattern matches expectations — browsing 50 videos costs 1 unit (/playlistItems) plus 1 per video detail view (/videos). At this rate, the free 10,000 unit daily quota supports thousands of page loads.

```
{
  "call_name": "Get Video Stats",
  "method": "GET",
  "path": "/videos",
  "parameters": [
    { "key": "id",   "value": "<dynamic: selected video ID>" },
    { "key": "part", "value": "statistics,snippet" }
  ],
  "use_as": "Data",
  "key_fields": [
    "items[0].statistics.viewCount",
    "items[0].statistics.likeCount",
    "items[0].statistics.commentCount",
    "items[0].snippet.description",
    "items[0].snippet.publishedAt"
  ],
  "quota_cost": "1 unit per call"
}
```

**Expected result:** The complete integration is working: video list loads via /playlistItems (1 quota unit), clicking a video embeds it via iframe and triggers a /videos call for stats (1 quota unit). Quota usage is verified in Google Cloud Console as well within daily limits.

## Best practices

- Always use GET /playlistItems for browsing a known channel's videos — it costs 1 quota unit vs. 100 for /search. This is the single most impactful quota optimization for YouTube Bubble apps.
- Mark the 'key' parameter as Private in Bubble's API Connector — without Private, the API key appears in browser network requests and can be extracted from DevTools by any visitor.
- Set quota alerts in Google Cloud Console at 80% of the daily limit (8,000 of 10,000 units) — traffic spikes can exhaust your quota quickly if /search is used anywhere in the app.
- Store the uploads playlist ID (retrieved via /channels once) in Bubble App Data rather than fetching it on every page load — the playlist ID for a channel never changes.
- Use the `fields` parameter in API calls to reduce response payload size where possible — for list views, request only `snippet/title,snippet/thumbnails,contentDetails/videoId` rather than the full snippet object.
- Handle the privacy edge case for subscriber and view counts — YouTube returns '0' when a creator opts out of public counts; display '—' rather than '0' to avoid misleading your users.
- For video course platforms hosting on YouTube, add access control at the Bubble level — YouTube videos marked as 'unlisted' are accessible to anyone with the URL; Bubble must control who sees the video ID, not YouTube's privacy settings.

## Use cases

### YouTube Channel Video Gallery

Display a YouTube channel's full video catalog in a Bubble repeating group — thumbnails, titles, view counts, and publish dates — with an embedded player that appears when a user clicks a video. Ideal for brand video pages, course platforms, and portfolio sites.

Prompt example:

```
Build a Bubble page that displays a YouTube channel's videos in a grid repeating group with thumbnail, title, view count, and date published. Clicking a video shows a YouTube iframe player in a popup and loads the full description and stats.
```

### Video Course Platform with YouTube Embeds

Host course videos on YouTube (for free storage and streaming) and manage course structure, student progress, and access control in Bubble. Use the YouTube API to fetch video metadata and durations while Bubble handles enrollment, payments, and progress tracking.

Prompt example:

```
Create a Bubble course page that loads videos from a specific YouTube playlist using playlistItems, displays them in an ordered lesson list, marks lessons as complete when the user clicks a Done button, and stores progress in the Bubble database.
```

### YouTube Analytics Dashboard for Creators

Build an internal Bubble dashboard for content creators showing their latest video performance — view counts, like counts, and comment counts from GET /videos — alongside Bubble-native data like internal campaign tracking or client notes.

Prompt example:

```
Build a Bubble dashboard where a content creator can paste video IDs and see a table of view count, like count, comment count, and duration for each video, fetched live from the YouTube API.
```

## Troubleshooting

### API returns 400 Bad Request or 'The request specifies an invalid filter' error

Cause: The 'part' parameter is missing, has an invalid value, or conflicting parameters are combined. Each YouTube endpoint requires specific part values — /playlistItems accepts 'snippet', 'contentDetails', 'status'; /channels accepts 'contentDetails', 'snippet', 'statistics'.

Solution: Check the part parameter value for the failing call. Use exactly the values documented for that endpoint — avoid copy-pasting part values from one endpoint to another. Re-initialize the call in Bubble's API Connector after fixing the parameter.

### Initialize call returns 'There was an issue setting up your call' or 403 Forbidden

Cause: YouTube Data API v3 is not enabled for your Google Cloud project, the API key is not authorized for YouTube, or the API key restriction is too narrow (e.g., restricted to a different API).

Solution: Go to Google Cloud Console → APIs & Services → Library → search 'YouTube Data API v3' and verify it shows 'API Enabled.' Check Credentials → your API key → API restrictions → confirm YouTube Data API v3 is in the allowed list. If you recently enabled the API, wait 1–2 minutes for propagation and re-initialize.

### Daily quota exhausted (quotaExceeded error) after relatively few page loads

Cause: The /search endpoint is being used for channel video browsing. Each /search call costs 100 quota units, exhausting the 10,000 unit daily free limit in 100 calls.

Solution: Replace all /search calls used for channel browsing with /playlistItems using the channel's uploads playlist ID — this costs 1 quota unit instead of 100. Reserve /search only for genuine user-initiated text searches across all of YouTube. Set quota alerts in Google Cloud Console at 80% usage to get advance warning.

### videoId is mapped incorrectly and the iframe embed shows an error

Cause: The videoId field is at different JSON paths depending on the endpoint: in /playlistItems it is at 'contentDetails.videoId'; in /videos it is the root 'id' field; in /search results it is at 'id.videoId'. Using the wrong path mapping in Bubble's API Connector response definition produces an incorrect or empty videoId.

Solution: Re-initialize the failing API call in Bubble and carefully check the response definition. Navigate the JSON tree to find the videoId at the correct path for that specific endpoint. Create separate initialized calls for /playlistItems and /videos rather than reusing the same response definition.

### subscriberCount shows '0' despite the channel having many subscribers

Cause: The channel owner has enabled the 'Hide my subscriber count' privacy option in YouTube Studio. YouTube returns '0' in the API response, not null or an error.

Solution: Add a conditional in Bubble: if subscriberCount equals '0' and you have reason to believe the channel has subscribers, display '—' instead of the number. This prevents displaying misleading information to users.

## Frequently asked questions

### Do I need Google OAuth to show YouTube videos in Bubble?

No, for public video data — video titles, thumbnails, view counts, playlist contents, and channel info — a Google Cloud API key is sufficient. Google OAuth is only needed for user-specific actions like commenting, liking, uploading, or managing a channel's content programmatically.

### Why should I use /playlistItems instead of /search for channel videos?

The /search endpoint costs 100 quota units per call; /playlistItems costs 1 unit. With the 10,000 unit free daily quota, /search exhausts the limit after just 100 calls while /playlistItems supports 10,000 calls. For browsing a known channel, always use /playlistItems with the channel's uploads playlist ID.

### How do I embed a YouTube video player in Bubble?

Bubble has no native YouTube video element. Use an HTML element and write a YouTube iframe embed: set the src to 'https://www.youtube.com/embed/{videoId}' with your dynamic videoId inserted via Bubble's dynamic expression picker. Set frameborder=0 and allowfullscreen on the iframe for full player functionality.

### What is the daily quota limit and what happens when it is exceeded?

The YouTube Data API v3 free tier provides 10,000 quota units per day. When exceeded, all API calls return a 403 quotaExceeded error until midnight Pacific Time when the quota resets. To request a higher quota, apply in Google Cloud Console → APIs & Services → Quotas → Request an increase. Using /playlistItems (1 unit) instead of /search (100 units) reduces consumption dramatically.

### Can Bubble control YouTube playback (play, pause, seek)?

Not directly through the YouTube Data API. The YouTube iframe Player API is a JavaScript library that can control an embedded player — you can use it via Bubble's 'Run JavaScript' action (with the Toolbox plugin) to trigger play, pause, and seek operations on an iframe player. This requires some JavaScript setup but is possible in Bubble.

### How do I find my YouTube Channel ID?

Go to YouTube Studio (studio.youtube.com), click Settings in the left sidebar, then Channel, then the Advanced Settings tab. Your Channel ID is shown there — it starts with 'UC' followed by 22 characters. You can also find it in the URL when viewing your channel page on YouTube: the portion after 'youtube.com/channel/' is the Channel ID.

---

Source: https://www.rapidevelopers.com/bubble-integrations/youtube-api
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/youtube-api
