TiktokExplode is a .NET library that lets you retrieve metadata and download videos from TikTok programmatically. It handles session management, cookie injection, and WAF/bot-detection bypassing behind a simple, clean API — so you can focus on using the data instead of fighting the platform.
The library follows Clean Architecture: the domain layer (TiktokExplode) has zero external dependencies and exposes immutable, strongly-typed models, while the infrastructure layer (TiktokExplode.Infrastructure) handles all HTTP and browser-based concerns.
- Fetch full video metadata: author, stats, duration, language, location, bitrates, and more
- Download videos without watermark or with watermark
- Download the static cover image (JPEG) or the animated cover (WebP)
- Search TikTok by keyword — streams results lazily via
IAsyncEnumerable<Media>(videos and carousels) - Carousel/slideshow support —
Carouselentity with full image collection metadata - Progress reporting during download via
IProgress<double> - Automatic WAF/bot-detection retry with configurable backoff
- Strategy pattern — choose between Playwright (reliable) or HTTP-only (lightweight) page fetching
- Clean Architecture — pure domain with zero external dependencies
- Targets net8.0 and net9.0
Recommended — install both packages in one command:
dotnet add package TiktokExplode.All
Or install individually:
dotnet add package TiktokExplode.Infrastructure
TiktokExplode.Infrastructureautomatically brings inTiktokExplode(domain) as a transitive dependency.
InstallTiktokExplodealone only if you need the domain models/interfaces without the infrastructure.
Using Microsoft.Extensions.DependencyInjection?
dotnet add package TiktokExplode.Extensions.DependencyInjection
Adds
AddTiktokExplode()onIServiceCollection. See the Dependency Injection section.
Note:
TiktokExplode.Infrastructuredepends on Microsoft.Playwright. After installation, run the following once to download the browser binaries:pwsh -c "playwright install chromium"
using TiktokExplode.Infrastructure.Clients;
using TiktokExplode.Infrastructure.Common;
await using var client = TiktokClient.CreateWithBrowser();
var video = await client.GetVideoAsync("https://www.tiktok.com/@user/video/1234567890");
Console.WriteLine($"ID: {video.Id}");
Console.WriteLine($"Author: {video.Author.Name} (@{video.Author.UniqueId})");
Console.WriteLine($"Duration: {video.Duration.Seconds}s");
Console.WriteLine($"Views: {video.Stats.Views}");
Console.WriteLine($"Likes: {video.Stats.Likes}");
// Download without watermark (stream + content length)
await using var streamInfo = await client.DownloadAsync(video);
await using var file = File.Create($"{video.Id}.mp4");
await streamInfo.Stream.CopyToAsync(file);
// Or use the extension to download directly to a file with progress
IProgress<double> progress = new Progress<double>(p => Console.Write($"\rProgress: {p:P0}"));
await client.DownloadAsync(video, $"{video.Id}.mp4", progress);TiktokClient uses the Strategy pattern to decouple page fetching from downloading. Use the factory methods to choose a strategy:
// Playwright — uses a real browser to bypass WAF (recommended)
await using var client = TiktokClient.CreateWithBrowser();
// Playwright with custom options
await using var client = TiktokClient.CreateWithBrowser(
new PlaywrightFetcherOptions { BrowserChannel = "msedge", Headless = true },
new TikTokOptions { MaxWafRetries = 5 });
// HTTP-only — lightweight, may be blocked by WAF
await using var client = TiktokClient.CreateWithHttp();
// Inject your own IPageFetcher implementation
await using var client = new TiktokClient(myFetcher, new TikTokOptions());| Method | Returns | Description |
|---|---|---|
GetVideoAsync(string url, CancellationToken) |
Video |
Fetches full video metadata |
DownloadAsync(Video, CancellationToken) |
StreamInfo |
Downloads video without watermark |
DownloadWatermarkedAsync(Video, CancellationToken) |
StreamInfo |
Downloads video with watermark |
DownloadImageAsync(CarouselImage, CancellationToken) |
StreamInfo |
Downloads a carousel image stream |
TiktokClient implements IAsyncDisposable — always use await using.
TiktokSearchClient searches TikTok by keyword and streams results as Media objects (either Video or Carousel) using IAsyncEnumerable<Media>. It requires Playwright because TikTok's search API signs requests with dynamic tokens that can only be generated by a real browser session.
await using var client = TiktokSearchClient.CreateWithBrowser();
await foreach (var media in client.SearchAsync("funny cats"))
{
Console.WriteLine($"{media.Author.UniqueId}: {media.Description}");
if (media is Video video)
{
// Standard video post
await client.DownloadAsync(video, $"{video.Id}.mp4");
}
else if (media is Carousel carousel)
{
// Slideshow post — contains a collection of images
Console.WriteLine($"Carousel with {carousel.Post.Images.Count} images");
}
}Note:
TiktokSearchClientimplementsISearchClient, which extendsIDownloadClient. This means all extension methods (DownloadAsync(filePath),DownloadWatermarkedAsync(filePath),DownloadImageAsync,DownloadAnimatedImageAsync,DownloadCarouselImagesAsync) are available directly on the search client — no need for a separateTiktokClientinstance.
Search scope: The current implementation returns results from the first page of TikTok's search API (~10–20 results).
| Method | Returns | Description |
|---|---|---|
SearchAsync(string keyword, CancellationToken) |
IAsyncEnumerable<Media> |
Streams media results (videos and carousels) for the keyword |
DownloadAsync(Video, CancellationToken) |
StreamInfo |
Downloads video without watermark |
DownloadWatermarkedAsync(Video, CancellationToken) |
StreamInfo |
Downloads video with watermark |
DownloadImageAsync(CarouselImage, CancellationToken) |
StreamInfo |
Downloads a carousel image stream |
TiktokSearchClient implements IAsyncDisposable — always use await using.
| Method | Description |
|---|---|
DownloadAsync(video, filePath, progress?, ct) |
Downloads video without watermark to a file, with optional progress |
DownloadWatermarkedAsync(video, filePath, progress?, ct) |
Downloads video with watermark to a file, with optional progress |
DownloadImageAsync(image, filePath, progress?, ct) |
Downloads a single carousel image to a file, with optional progress |
DownloadCarouselImagesAsync(carousel, directoryPath, progress?, ct) |
Downloads all carousel images to a directory with overall progress |
DownloadImageAsync(video, filePath, ct) |
Downloads the static cover image of a video (JPEG) |
DownloadAnimatedImageAsync(video, filePath, ct) |
Downloads the animated cover of a video (WebP) |
// Download video to file with optional progress
IProgress<double> progress = new Progress<double>(p => Console.Write($"\rProgress: {p:P0}"));
await client.DownloadAsync(video, "output.mp4", progress);
await client.DownloadWatermarkedAsync(video, "output_wm.mp4", progress);
// Download cover images
await client.DownloadImageAsync(video, "cover.jpg");
await client.DownloadAnimatedImageAsync(video, "cover.webp");
// Download a single carousel image
await client.DownloadImageAsync(carousel.Post.Images[0], "image_1.jpg", progress);
// Download all carousel images to a directory (named image_1.jpg, image_2.jpg, ...)
await client.DownloadCarouselImagesAsync(carousel, "my_carousel_folder", progress);Note: Animated covers are served by TikTok as animated WebP files. Not all videos have an animated cover — if
Cover.AnimatedUrlis empty, the video only has a static cover.
ContentLength is sourced from the CDN response headers — always accurate, no estimate from metadata.
Returned by DownloadAsync and DownloadWatermarkedAsync. Implements IAsyncDisposable.
| Property | Type | Description |
|---|---|---|
Stream |
Stream |
The video content stream |
ContentLength |
long |
Exact file size in bytes from CDN |
| Property | Default | Description |
|---|---|---|
MaxWafRetries |
3 |
Max retries on WAF detection |
RetryBaseDelay |
2s |
Base delay between retries (grows linearly) |
| Property | Default | Description |
|---|---|---|
BrowserChannel |
null |
Browser channel (e.g. "msedge", "chrome"). null uses Playwright's bundled Chromium |
Headless |
true |
Run browser in headless mode |
PageTimeoutMs |
30000 |
Navigation timeout in milliseconds |
| Property | Default | Description |
|---|---|---|
UserAgent |
Chrome 136 UA | User-Agent header sent with requests |
WarmupDelay |
1200ms |
Delay after warmup request before fetching |
All search results are Media objects. Use pattern matching (is Video, is Carousel) to access type-specific properties.
| Property | Type | Description |
|---|---|---|
Id |
string |
TikTok post ID |
Description |
string |
Caption / description |
Author |
Author |
Author entity |
Stats |
MediaStats |
Views, likes, comments, shares, favorites, reposts |
Language |
MediaLanguage |
Detected content language |
Location |
string |
Location tag (if any) |
Cover |
MediaCover |
Static (JPEG) and animated (WebP) cover image URLs |
Music |
MediaMusic |
Track metadata and artwork |
CreatedAt |
DateTimeOffset |
Upload date |
| Property | Type | Description |
|---|---|---|
Info |
VideoInfo |
Technical info, bitrates, and download URLs |
Duration |
VideoDuration |
Duration in seconds and precise seconds |
Represents a TikTok slideshow post (multiple images + audio).
| Property | Type | Description |
|---|---|---|
Post |
CarouselPost |
Collection of images with their URLs |
| Property | Type | Description |
|---|---|---|
Id |
string |
Internal TikTok user ID |
UniqueId |
string |
Handle (e.g. johndoe) |
Name |
string |
Display name |
Description |
string |
Bio |
IsVerified |
bool |
Verified badge |
IsPrivate |
bool |
Private account |
Avatar |
ProfileImageVariants |
Avatar image URLs (small, medium, large) |
Stats |
AuthorStats |
Followers, following, friends, likes received, video count |
CreatedAt |
DateTimeOffset |
Account creation date |
TiktokExplode.Extensions.DependencyInjection provides a fluent AddTiktokExplode() extension method for registering all TiktokExplode services into the .NET DI container.
// Default — Playwright fetcher, all defaults
services.AddTiktokExplode();
// Custom — Playwright with visible browser window
services.AddTiktokExplode(b => b
.UsePlaywrightFetcher(o => o.Headless = false));
// HTTP fetcher — lighter, no browser dependency
// Note: ISearchClient is NOT available with HTTP fetcher (see below)
services.AddTiktokExplode(b => b
.UseHttpFetcher(o => o.WarmupDelay = TimeSpan.Zero)
.ConfigureTiktok(o => o.MaxWafRetries = 5));Registered services:
| Service | Implementation | Lifetime | Fetcher |
|---|---|---|---|
IVideoClient |
TiktokClient |
Singleton | Both |
ISearchClient |
TiktokSearchClient |
Singleton | Playwright only |
IPageFetcher |
PlaywrightFetcher or HttpFetcher |
Singleton | Both |
ISearchFetcher |
PlaywrightSearchFetcher |
Singleton | Playwright only |
TikTokOptions |
— | Singleton | Both |
PlaywrightFetcherOptions or HttpFetcherOptions |
— | Singleton | Both |
Important:
ISearchClientandISearchFetcherare only registered when using the Playwright fetcher (the default). If you callUseHttpFetcher(), these services will not be present in the container. Attempting to injectISearchClientwith an HTTP-configured builder will throw a DI resolution error at runtime. This is by design — TikTok's search API requires a real browser to generate signed requests.
// Consume in your services via constructor injection
public class MyService(IVideoClient client)
{
public async Task<string> GetTitleAsync(string url)
{
var video = await client.GetVideoAsync(url);
return video.Description;
}
}using TiktokExplode.Domain.Exceptions;
try
{
var video = await client.GetVideoAsync(url);
}
catch (TiktokWafException ex)
{
// Bot detection triggered after all retries exhausted
}
catch (VideoNotFoundException ex)
{
// Video does not exist or is private
}
catch (TiktokParsingException ex)
{
// Unexpected page structure (TikTok changed their HTML/JSON)
}
catch (TiktokException ex)
{
// Base exception — catch-all for library errors
}TiktokExplode/ # Domain — zero external dependencies
Domain/
Entities/ # Media (abstract), Video, Carousel, Author
ValueObjects/
Media/ # MediaStats, MediaLanguage, MediaCover, MediaMusic, MediaDuration
Videos/ # VideoInfo, VideoDuration, VideoDownloadLinks, Bitrate
Carousels/ # CarouselPost and related types
Authors/ # AuthorStats, ProfileImageVariants
Abstractions/ # IVideoClient, ISearchClient, IDownloadClient
Exceptions/ # TiktokException hierarchy
Utilities/ # URL validation
TiktokExplode.Infrastructure/ # HTTP + browser automation (Playwright + AngleSharp)
Clients/ # TiktokClient : IVideoClient, TiktokSearchClient : ISearchClient
Fetchers/ # IPageFetcher, PlaywrightFetcher, HttpFetcher
Fetchers/Search/ # ISearchFetcher, PlaywrightSearchFetcher
Http/ # TikTokDownloadClient — CDN download management
Browser/ # TikTokBrowser — internal Playwright wrapper
Parsers/ # TikTokVideoParser, TikTokSearchParser — JSON extraction
Options/ # TikTokOptions, PlaywrightFetcherOptions, HttpFetcherOptions
Common/ # StreamExtensions, TiktokClientExtensions
TiktokExplode.All/ # Meta-package — installs both packages above in one command
TiktokExplode.Extensions.DependencyInjection/ # AddTiktokExplode() for Microsoft.Extensions.DI
This library was heavily inspired by YoutubeExplode by Tyrrrz. His work showed me how a well-designed, clean .NET library for a media platform should look and feel. Without that reference, TiktokExplode would not exist. Thank you.
MIT — see LICENSE for details.