Python 3.9+ · Sync + Async

YouTube search for Python, without the API-key overhead.

Search videos, playlists and channels; work with recommendations, suggestions, comments, transcripts, hashtags, Innertube data and stream formats from one compact Python library. The public API stays familiar while transport and resource ownership are centralized for long-running applications.

Sync + AsyncStandard and future namespaces
No API keyNo YouTube Data API v3 quota
InnertubeSearch, playlists and content flows
Stream awareFormats, PO-token handoff and unresolved states

Install

Published on PyPI as yt-search-python.

terminal
pip install yt-search-python
Async uses the same project.
Import async classes from youtubesearchpython.future. For transcript fallback support, install yt-search-python[transcript].

Quick start

Simple search stays small; pagination is explicit.

search.py
from youtubesearchpython import VideosSearch

search = VideosSearch("Arijit Singh", limit=10)
print(search.result())

search.next()
print(search.result())

API reference

Open a method to see its signature. Examples and response shapes stay collapsed until you ask for them.

Tap / click any method
Search / VideosSearchGeneral search, video-only search, live-only filtering and pagination+
VideosSearch(query, limit=20, language="en", region="US", timeout=None, is_live=None)

Use Search for mixed results or VideosSearch for videos. Call next() for the next page. is_live=True enables live-only search.

SyncAsyncPaginationLive filter
Example
videos_search.py
from youtubesearchpython import VideosSearch

search = VideosSearch("news", limit=10, is_live=True)
first = search.result()

search.next()
second = search.result()
Response shape
{
  "result": [
    {
      "type": "video",
      "id": "VIDEO_ID",
      "title": "...",
      "publishedTime": "...",
      "duration": "...",
      "viewCount": {...},
      "thumbnails": [...],
      "channel": {...},
      "link": "https://www.youtube.com/watch?v=..."
    }
  ]
}
ChannelsSearch / PlaylistsSearchSearch channels or playlists with the same paginated interface+
ChannelsSearch(query, limit=20, language="en", region="US", timeout=None) PlaylistsSearch(query, limit=20, language="en", region="US", timeout=None)

Both classes support result() and next() like the other search classes.

Example
typed_search.py
from youtubesearchpython import ChannelsSearch, PlaylistsSearch

channels = ChannelsSearch("Google Developers", limit=5)
playlists = PlaylistsSearch("Python tutorial", limit=5)

print(channels.result())
print(playlists.result())
Response shape
{
  "result": [
    {
      "type": "channel | playlist",
      "id": "...",
      "title": "...",
      "thumbnails": [...],
      "link": "..."
    }
  ]
}
CustomSearchApply YouTube search preference strings for custom filtering and sorting+
CustomSearch(query, searchPreferences, limit=20, language="en", region="US", timeout=None)

Use preference constants such as SearchMode, VideoUploadDateFilter, VideoDurationFilter and VideoSortOrder when constructing a custom search preference.

Example
custom_search.py
from youtubesearchpython import CustomSearch, SearchMode

search = CustomSearch(
    "Python",
    SearchMode.videos,
    limit=10,
)
print(search.result())
Response shape
{
  "result": [
    {
      "type": "...",
      "id": "...",
      "title": "...",
      "...": "fields depend on the selected search mode"
    }
  ]
}
ChannelSearchSearch inside a specific channel browse ID+
ChannelSearch(query, browseId, language="en", region="US", searchPreferences="EgZzZWFyY2g%3D", timeout=None)

Useful when the query should be scoped to one channel rather than global YouTube search.

Example
channel_search.py
from youtubesearchpython import ChannelSearch

search = ChannelSearch(
    "Python",
    "UC_x5XG1OV2P6uZZ5FSM9Ttw",
)
print(search.result())
search.next()
Response shape
{
  "result": [
    {
      "id": "VIDEO_OR_PLAYLIST_ID",
      "title": "...",
      "thumbnails": [...],
      "link": "..."
    }
  ]
}
VideoVideo metadata, player information and format extraction+
Video.getInfo(videoLink, mode=ResultMode.dict, timeout=None, po_token=None, visitor_data=None, proxy=None) Video.getFormats(videoLink, mode=ResultMode.dict, timeout=None, po_token=None, visitor_data=None, proxy=None)

PO token and visitor data are optional inputs for sessions/clients where YouTube requires them.

Example
video.py
from youtubesearchpython import Video

info = Video.getInfo("pnxL4OOzPEc")
formats = Video.getFormats(
    "pnxL4OOzPEc",
    po_token="TOKEN",
    visitor_data="VISITOR_DATA",
)
Response shape
{
  "title": "...",
  "id": "VIDEO_ID",
  "duration": {...},
  "viewCount": {...},
  "channel": {...},
  "description": "...",
  "thumbnails": [...],
  "formats": [...]
}
PlaylistRegular playlists plus native YouTube Mix / Radio playlists+
Playlist.get(playlistLink, mode=ResultMode.dict, timeout=None) Playlist.getInfo(...) · Playlist.getVideos(...) · Playlist(link).getNextVideos()

Regular playlists support continuation pages. Mix/Radio playlists (RD...) use YouTube's native next flow and preserve returned order.

Example
playlist.py
from youtubesearchpython import Playlist

data = Playlist.get("PLAYLIST_ID")

playlist = Playlist("PLAYLIST_ID")
next_page = playlist.getNextVideos()

mix = Playlist.get(
    "https://youtube.com/playlist?list=RDpnxL4OOzPEc&playnext=1"
)
Response shape
{
  "id": "PLAYLIST_ID",
  "title": "...",
  "channel": {...},
  "thumbnails": [...],
  "videos": [
    {
      "id": "VIDEO_ID",
      "title": "...",
      "duration": "...",
      "thumbnails": [...]
    }
  ]
}
RecommendationsRelated videos with stable ordering and duplicate removal+
Recommendations.get(videoId, timeout=None)

The source video is skipped and duplicate IDs are removed without re-sorting YouTube's returned order.

Example
recommendations.py
from youtubesearchpython import Recommendations

related = Recommendations.get("pnxL4OOzPEc")
print(related)
Response shape
[
  {
    "id": "RELATED_VIDEO_ID",
    "title": "...",
    "thumbnails": [...],
    "channel": {...},
    "link": "..."
  }
]
SuggestionsQuery suggestions with language / region support and reusable sessions+
Suggestions.get(query, language="en", region="US", timeout=None, mode=ResultMode.dict) Suggestions.session(language="en", region="US", timeout=None)
Example
suggestions.py
from youtubesearchpython import Suggestions

print(Suggestions.get("Guru Randhawa"))

session = Suggestions.session(language="en", region="US")
print(session.get("Python"))
Response shape
{
  "result": [
    "suggestion one",
    "suggestion two",
    "..."
  ]
}
CommentsVideo comments with continuation support+
Comments.get(videoLink, mode=ResultMode.dict, timeout=None) Comments(videoLink).getNextComments()
Example
comments.py
from youtubesearchpython import Comments

first = Comments.get("pnxL4OOzPEc")

comments = Comments("pnxL4OOzPEc")
comments.init()
next_page = comments.getNextComments()
Response shape
{
  "result": [
    {
      "content": "...",
      "author": {...},
      "publishedTime": "...",
      "likeCount": "...",
      "replyCount": "..."
    }
  ]
}
TranscriptNative caption/player flow with optional legacy fallback extra+
Transcript.get(videoLink, params=None, mode=ResultMode.dict, timeout=None)

Use params for the desired caption language/track parameters. The optional transcript extra preserves the legacy fallback path.

Example
transcript.py
from youtubesearchpython import Transcript

transcript = Transcript.get(
    "pnxL4OOzPEc",
    params="en",
)
print(transcript)
Response shape
{
  "result": [
    {
      "text": "...",
      "start": "...",
      "duration": "..."
    }
  ]
}
ChannelChannel info or playlists with explicit request type+
Channel.get(channelId, mode=ResultMode.dict, timeout=None) Channel(channel_id, request_type=ChannelRequestType.playlists, timeout=None)
Example
channel.py
from youtubesearchpython import Channel, ChannelRequestType

info = Channel.get("UC_x5XG1OV2P6uZZ5FSM9Ttw")

channel = Channel(
    "UC_x5XG1OV2P6uZZ5FSM9Ttw",
    request_type=ChannelRequestType.playlists,
)
channel.init()
channel.next()
Response shape
{
  "id": "CHANNEL_ID",
  "title": "...",
  "description": "...",
  "thumbnails": [...],
  "playlists": [...]
}
HashtagHashtag content with limit, language and region controls+
Hashtag.get(hashtag, mode=ResultMode.dict, limit=60, language="en", region="US", timeout=None)
Example
hashtag.py
from youtubesearchpython import Hashtag

music = Hashtag.get(
    "music",
    limit=10,
    language="en",
    region="US",
)
print(music)
Response shape
{
  "result": [
    {
      "type": "video | short",
      "id": "...",
      "title": "...",
      "thumbnails": [...]
    }
  ]
}
StreamURLFetcherDirect/already-signed formats, unresolved cipher reporting and PO-token handoff+
StreamURLFetcher(proxy=None, cookies_file=None, po_token=None, visitor_data=None) get(videoFormats_or_id, itag, po_token=None) · getAll(videoFormats_or_id, po_token=None)

The fetcher does not depend on yt-dlp. Formats that still require encrypted player-JavaScript deciphering are surfaced under unresolved; URLs that retain an n challenge are marked throttled.

PO-token generation and session-aware caching can be handled separately by ytsp-po-token-provider ↗.

Example
stream.py
from youtubesearchpython import StreamURLFetcher

fetcher = StreamURLFetcher(
    po_token="YOUR_PO_TOKEN",
    visitor_data="YOUR_VISITOR_DATA",
)

url = fetcher.get("pnxL4OOzPEc", 18)
all_formats = fetcher.getAll("pnxL4OOzPEc")
Response shape
{
  "streams": [
    {
      "itag": 18,
      "url": "https://...",
      "mimeType": "...",
      "throttled": false
    }
  ],
  "unresolved": [
    {
      "itag": "...",
      "reason": "signature deciphering required"
    }
  ]
}
Async APISame high-level API under youtubesearchpython.future+

Search classes load the first page on the first awaited next(). Content methods such as Video.getInfo, Playlist.get, Recommendations.get and StreamURLFetcher.getAll are awaitable in the future namespace.

Example
async_search.py
import asyncio
from youtubesearchpython.future import VideosSearch

async def main():
    search = VideosSearch("Arijit Singh", limit=10)
    first = await search.next()
    second = await search.next()
    print(first, second)

asyncio.run(main())
Response shape
Same logical result structures as the synchronous API.
The difference is lifecycle: network operations are awaited.
Result modes & filtersResultMode, SearchMode, upload date, duration, sort order and channel request types+
ResultMode.dict · ResultMode.json SearchMode.videos · channels · playlists · livestreams VideoUploadDateFilter.lastHour · today · thisWeek · thisMonth · thisYear VideoDurationFilter.short · long VideoSortOrder.relevance · uploadDate · viewCount · rating ChannelRequestType.info · playlists
Example
modes.py
from youtubesearchpython import (
    ResultMode,
    SearchMode,
    VideoSortOrder,
)

# Result mode is accepted by content APIs.
# Search/filter constants expose the preference values used by YouTube search.

print(ResultMode.dict)
print(SearchMode.videos)
print(VideoSortOrder.relevance)
Response shape
ResultMode.dict -> Python dictionaries/lists
ResultMode.json -> JSON string output where supported
HTTP lifecycleManaged clients with optional explicit teardown+
close_clients() · await aclose_clients()

Normal sync applications require no explicit shutdown call. Async clients are owned by their event loops and close when their loop shuts down gracefully. Explicit teardown remains available for tests or unusual lifecycle control.

Example
cleanup.py
# Optional forced teardown only
from youtubesearchpython import close_clients
close_clients()

# Async:
from youtubesearchpython.future import aclose_clients
await aclose_clients()

Designed for long-running services

Transport ownership is centralized instead of letting independent components accumulate their own client pools.

Centralized transport

One canonical HTTP layer reduces duplicate client creation and makes resource ownership predictable.

Async lifecycle

Async clients are associated with their owning event loop and clean up with normal loop shutdown.

Compatibility

Python 3.9+, runtime-tested on Python 3.13.5 and audited against Python 3.14 asyncio removals/deprecations.

Support

Choose the right Telegram destination. The labels are clickable; raw URLs stay out of the interface.