Skip to content
ytb

The local store

Persist everything you fetch into SQLite, then crawl it with a work queue and query it with SQL.

Most commands are stateless: they stream straight to stdout and keep nothing. Pass the global --db <path> flag and ytb also writes everything it fetches into a SQLite database (videos, channels, playlists, comments, caption tracks, formats, and the relationships between them). The same commands you already run become a crawler, and you get SQL over what you have collected.

ytb channel @MrBeast --videos --db yt.db   # stream and persist in one pass
ytb video dQw4w9WgXcQ --db yt.db            # one video, with its relations

The store is pure Go (modernc.org/sqlite), so nothing links libsqlite and the binary stays static. Without --db, no database is ever created: the flag is the only thing that turns persistence on.

Building a crawl queue

For larger collection runs, seed, crawl, queue, and jobs turn the store into a work queue that a pool of workers drains. All four need --db.

seed loads a worklist. Pass a single item, or --file to load a newline-delimited list, and --entity to tag the kind (video, channel, playlist, search, hashtag, or community). Use --priority to push entries to the front.

ytb seed @MrBeast --entity channel --db yt.db
ytb seed --file ids.txt --entity video --db yt.db
ytb seed "lofi" --entity search --priority 10 --db yt.db

search can enqueue its own results directly with --enqueue:

ytb search "podcast" -o id --enqueue --db yt.db   # seed the queue from a search

crawl drains the queue with -j workers, persisting each result as it goes. --max-per-item caps how many items a single queue entry yields, and --entity restricts the run to one kind.

ytb crawl --db yt.db -j 8                  # drain with 8 workers
ytb crawl --db yt.db --entity video --max-per-item 200

queue shows what is pending, and --status filters by pending, done, or failed. jobs prints the recent crawl job history.

ytb queue --db yt.db                        # pending work
ytb queue --status failed --db yt.db        # what went wrong
ytb jobs --db yt.db                          # recent runs

Querying the store

The db command group inspects and queries the SQLite store. It is read-only by nature (queries cannot mutate), pure Go, with no cgo.

db stats reports row counts per table. db path prints where the file lives.

ytb db stats --db yt.db
ytb db path --db yt.db

db query runs a read-only SQL statement against the store. Tables follow the data models, so a videos table carries columns like title, view_count, and channel_name:

ytb db query "select title, view_count from videos order by view_count desc limit 10" --db yt.db
ytb db query "select channel_name, count(*) from videos group by channel_name" --db yt.db

db search runs a full-text search over stored data. It searches videos by default; pass --channels to search channels instead.

ytb db search "mukbang" --db yt.db          # over stored video titles
ytb db search "tech" --channels --db yt.db

db vacuum compacts the database file. db reset drops and recreates all tables (it clears everything, so it prompts unless you pass -y).

ytb db vacuum --db yt.db
ytb db reset --db yt.db -y

Exporting to Markdown

export renders the stored data as an interlinked Markdown site under --out: per-video pages with YAML frontmatter, chapter lists, transcripts, related sidebars, and channel and playlist index pages. With no argument it exports every channel in the store; pass a channel id or @handle to scope it. It needs --db.

ytb export --db yt.db --out site/           # the whole store
ytb export @MrBeast --db yt.db --out site/  # one channel

These commands need a store

crawl, queue, jobs, export, the db subcommands, and search --enqueue all operate on the store, so they require --db. Run them without it and ytb errors clearly rather than silently doing nothing, since without --db there is no database to act on.