Skip to content

Everything for WordPress, web development — and beyond

📺 How to get videos from a YouTube playlist via API: working code in 15 minutes

📺 How to get videos from a YouTube playlist via API: working code in 15 minutes

You dropped a YouTube video link into the editor, it works. But when you need an entire playlist on your site that automatically picks up new videos, copying links manually every time turns into a chore.

A script in PHP with YouTube Data API v3 solves the problem completely: it pulls all videos from any public playlist, returns JSON, and you display them however you like, as a grid of cards, a list, a gallery. Setup takes 15 minutes, you only need an API key and basic PHP.

Below is working code for WordPress with error handling, pagination and caching. The approach is universal: it works in any PHP project, not just in WP.

💡 Quick overview:

  • Create an API key in Google Cloud Console and link it to YouTube Data API v3
  • Send a request to playlistItems and get JSON with all playlist videos
  • Parse the response, collect thumbnails and links, display them in a grid on the page
  • Add pagination for playlists longer than 50 videos and caching via set_transient()

Step 1: Create a YouTube Data API v3 key

An API key identifies your application to YouTube and tracks quotas. Created for free, daily limit is 10,000 units. One key is more than enough for most sites.

Go to Google Cloud Console with your Google account. Create a new project, project selection button is in the top bar, then New Project. Name it meaningfully, for example my-youtube-feed.

After creating the project, go to APIs & Services → Library. In the search bar type YouTube Data API v3 and click Enable. Without this step the key won't link to the right API, and requests will return 403.

Now, the key itself. In the side menu open Credentials, click Create Credentials → API key. The system will generate a key and show it in a modal window. Copy it right away: after closing the window the key is visible in the list, but its value is partially hidden.

Be sure to click Restrict key. In the API restrictions section select YouTube Data API v3, then the key won't work for other APIs even if it ends up in a public repository. For local development HTTP-referrer can be left empty.

The key looks like AIzaSyD-.... Save it in wp-config.php via define('YOUTUBE_API_KEY', '...') and add wp-config.php to .gitignore. Don't hardcode the key in theme code.

Step 2: Get videos from a playlist

For the request you need two things: an API key and a playlist ID. The ID is extracted from the URL: open the playlist on YouTube, copy the address bar. The list= parameter in the URL is the playlist ID: for example, PLp0YhAQYkolGq1e6r1m5....

Basic PHP request to the playlistItems endpoint:

1$api_key = 'AIzaSy...'; // your key from step 1
2$playlist_id = 'PLp0YhAQYkolG...'; // playlist ID from URL
3
4$api_url = 'https://www.googleapis.com/youtube/v3/playlistItems' .
5 '?part=snippet' .
6 '&maxResults=50' .
7 '&playlistId=' . urlencode($playlist_id) .
8 '&key=' . $api_key;
9
10$response = file_get_contents($api_url);
11$data = json_decode($response, true);
12
13if (json_last_error() !== JSON_ERROR_NONE) {
14 die('JSON parsing error: ' . json_last_error_msg());
15}

What's happening here. part=snippet requests basic data: title, description, thumbnail, position in playlist. For a full set you can specify snippet,contentDetails,status. maxResults=50 is the maximum per request, API returns 5 records by default, so it's better to specify the parameter explicitly. Encode playlistId via urlencode() in case of special characters.

For production replace file_get_contents() with wp_remote_get() (in WordPress) or cURL with timeout. A direct call will fail if Google is temporarily unavailable or the network is slow. Here's a version with HTTP error handling:

1$response = wp_remote_get($api_url, [
2 'timeout' => 15,
3 'headers' => ['Accept' => 'application/json'],
4]);
5
6if (is_wp_error($response)) {
7 error_log('YouTube API error: ' . $response->get_error_message());
8 return [];
9}
10
11$http_code = wp_remote_retrieve_response_code($response);
12if ($http_code !== 200) {
13 error_log('YouTube API HTTP ' . $http_code);
14 return [];
15}
16
17$body = wp_remote_retrieve_body($response);
18$data = json_decode($body, true);

Two levels of checking: first is_wp_error() catches network failures, then we verify the HTTP code. If something goes wrong, we return an empty array, the site doesn't crash.

Step 3: Display the video list on a page

The API returns an items array. Each element is one playlist video. Response structure:

1{
2 "items": [
3 {
4 "snippet": {
5 "title": "Video title",
6 "description": "Description...",
7 "thumbnails": {
8 "default": { "url": "https://i.ytimg.com/.../default.jpg" },
9 "medium": { "url": "https://i.ytimg.com/.../mqdefault.jpg" },
10 "high": { "url": "https://i.ytimg.com/.../hqdefault.jpg" }
11 },
12 "resourceId": {
13 "videoId": "dQw4w9WgXcQ"
14 }
15 }
16 }
17 ]
18}

A video is identified by resourceId.videoId. From it the link https://www.youtube.com/watch?v=<videoId> is assembled. Thumbnails are in thumbnails at three resolutions: for a card grid take medium, for a compact list, default.

Output code for WordPress, shortcode or page template:

1if (! empty($data['items'])) {
2 echo '<div class="yt-playlist-grid">';
3
4 foreach ($data['items'] as $item) {
5 $title = esc_html($item['snippet']['title']);
6 $video_id = esc_attr($item['snippet']['resourceId']['videoId']);
7 $thumbnail = esc_url($item['snippet']['thumbnails']['medium']['url']);
8 $link = 'https://www.youtube.com/watch?v=' . $video_id;
9
10 printf(
11 '<a href="%s" class="yt-card" target="_blank" rel="noopener">'
12 . '<img src="%s" alt="%s" loading="lazy">'
13 . '<span>%s</span></a>',
14 $link, $thumbnail, $title, $title
15 );
16 }
17
18 echo '</div>';
19}

Three things that save debugging time:

  • loading="lazy". With 50 videos on a page without lazy loading PageSpeed will drop. The attribute tells the browser: load the image only when scrolled to it.
  • esc_html()** and esc_url().** Mandatory sanitization for WordPress. Video titles sometimes contain quotes and HTML entities, without escaping they'll break the layout.
  • target="_blank"** with rel="noopener".** Open YouTube in a new tab, but don't give it access to window.opener, protection against tab-napping.

CSS for the grid, minimal, for a standard theme:

1.yt-playlist-grid {
2 display: grid;
3 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
4 gap: 20px;
5}
6.yt-card {
7 text-decoration: none;
8 color: inherit;
9 border-radius: 8px;
10 overflow: hidden;
11 transition: transform 0.2s;
12}
13.yt-card:hover {
14 transform: translateY(-2px);
15}
16.yt-card img {
17 width: 100%;
18 aspect-ratio: 16 / 9;
19 object-fit: cover;
20}
21.yt-card span {
22 display: block;
23 padding: 10px;
24 font-weight: 600;
25 font-size: 14px;
26}

The grid automatically adjusts to screen width: auto-fill + minmax(280px, 1fr) give from one to several columns without media queries.

Step 4: Pagination, quotas and common errors

Pagination. One request returns a maximum of 50 videos. If there are more in the playlist, a nextPageToken field appears in the response. Pass it in the next request, the API will return the next page. Loop:

1$all_items = [];
2$page_token = null;
3
4do {
5 $url = $api_url . '&pageToken=' . urlencode($page_token ?? '');
6 // ... API request ...
7 $all_items = array_merge($all_items, $data['items'] ?? []);
8 $page_token = $data['nextPageToken'] ?? null;
9} while ($page_token && count($all_items) < 500);

The loop breaks at 500 videos, a reasonable ceiling for a site page. Without a limit you risk using up the entire quota in a couple of loads.

Quotas. Each playlistItems.list call costs 1 quota unit, this is confirmed by Google's official quota table. Daily limit is 10,000 units. That's 10,000 requests per day, more than enough for the vast majority of sites. But if the playlist updates frequently, add caching:

1$cache_key = 'yt_playlist_' . md5($playlist_id);
2$cached_data = get_transient($cache_key);
3
4if ($cached_data !== false) {
5 return $cached_data;
6}
7
8// ... API request ...
9
10set_transient($cache_key, $data, HOUR_IN_SECONDS * 6);

Six hours is a reasonable balance between freshness and quota economy. For non-critical playlists set 12-24 hours. get_transient() and set_transient() are native WordPress functions, they work with object cache (Redis/Memcached) if it's configured, otherwise they write to wp_options.

Common errors and what to do about them:

  • 403 Forbidden. API is not enabled for the project in Google Cloud Console. Go back to step 1: the Enable button for YouTube Data API v3 must be clicked. Also check if there's an IP restriction in the key settings.
  • 400 Bad Request, «API key not valid». The key was just created and hasn't activated yet. Wait 2-5 minutes: API restrictions don't propagate instantly.
  • 404 Not Found. Invalid playlist ID, or the playlist is private. An API key returns only public and unlisted playlists. Private ones require OAuth authorization.
  • Empty items array with valid ID. Most likely the playlist is empty. Open the playlist URL in a browser and make sure there are videos.

💻 Video: YouTube Data API v3 in action

A short video on the topic, visually shows the whole process from creating a key to displaying a video list:

⁉️🤔 Frequently asked questions

Does the API work with private playlists?

No. playlistItems.list with an API key returns only public and unlisted playlists. For private ones you need OAuth authorization: the user must explicitly grant your application access to their account. For a public aggregator site an API key is enough.

Can I get videos from someone else's playlist?

Yes, if the playlist is public. An API key is not tied to the playlist owner, it identifies your application, not the YouTube user. Any public playlist is accessible by ID, regardless of who created it.

How do I get more than 50 videos at once?

You can't, it's a hard limit of maxResults for playlistItems, fixed in Google's documentation. Use pagination via nextPageToken (step 4). For a playlist of 300 videos you'll need 6 sequential requests, together they'll spend 6 quota units out of 10,000 daily.

Why aren't video thumbnails showing?

Check the URL in thumbnails: the field is called url, not link and not src. Second reason, ad blockers sometimes cut the i.ytimg.com domain. Third: some old videos (before 2010) don't have high-resolution thumbnails, take default, it's always there.

What to do if the key is compromised?

Immediately go to Google Cloud Console → Credentials, find the key and click Delete. Create a new one. The old key is deactivated within 5 minutes. Store the key via define('YOUTUBE_API_KEY', '...') in wp-config.php and exclude this file from the repository via .gitignore.

If one static playlist via iframe is enough for the site, you don't need the API, YouTube provides ready-made embed code. But as soon as you need to automatically pick up new videos, filter by date, customize layout or display thumbnails in a non-standard design, the API becomes the only working option.

For a WordPress site the wp_remote_get() + set_transient() combo completely eliminates quota and speed concerns: once every few hours the script fetches JSON, caches it, and the frontend works with the cache instantly. The playlist updates itself, the editor doesn't need to click anything.

If your theme supports custom PHP in templates, take the code from step 3, change the CSS to match your design. If you're using a page builder, wrap the PHP logic in a shortcode and insert it anywhere via [youtube_playlist id="PLp0..."].

Try it with one playlist: 15 minutes for a key and the first request, and you'll see if the approach works for your task. And if you've already used the API for other purposes, write in the comments which endpoint turned out to be the most useful.