youtubeuploader: a single Go binary that uploads video to YouTube
Scripted uploads to Youtube
At a glance
- What is it?
- youtubeuploader is a small Go command line tool that uploads a video file to YouTube through the Data API v3, with a browser based OAuth flow on first run and a cached token for everything after that. Its most useful feature is not the upload itself but the rate limiting, which lets a machine drip-feed uploads inside a daily quota window.
- Who is it for?
- youtubeuploader fits a team with a build or render pipeline that produces videos on a schedule and needs to push them to a channel without a person opening a browser, and it fits a creator who wants to stay inside the YouTube upload quota while uploading a backlog.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 76 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The upload is easy, the quota is the hard part
The project description is two words, scripted uploads to youtube, and the two capabilities the README highlights are upload video files from local disk or from the web, and rate limit upload bandwidth.
That second bullet is the more interesting one, and it exists because of a platform constraint rather than a client feature. The documentation states that by default you will only be able to upload about six videos every 24 hours due to quota restrictions set by Google, and it links the issue where this is discussed. An unauthenticated project talking to the YouTube Data API gets a small daily allowance, and a pipeline that produces more than that needs either several days of patience or several authorised projects.
The tool's response is to let you shape the upload schedule. There is a -ratelimit flag to rate limit upload in Kbps, with no limit by default, and a -limitBetween flag to only rate limit between these times, with an example given as 10:00-14:00 in the local time zone. So a pipeline can be configured to upload slowly, and to do its uploading only inside a window, which is how you work within a quota without a job that fails halfway.
The second quota-shaped detail is the chunk size. The -chunksize flag sets the size in bytes of each upload chunk, with a default of 16777216, and a zero value causes all data to be uploaded in a single request. Sixteen megabyte chunks is the resumable upload path, and it matters for a large file on an unreliable connection, because a failed chunk is retried rather than the whole file being sent again.
So the tool is really two things: a correct Data API client, and a policy layer for staying inside a quota.
Getting the OAuth client right is the part that fails first
The setup section walks through the Google Developers Console step by step, and the steps are worth following exactly because a mismatch surfaces at authorisation time.
You create an account on the Google Developers Console and a new project for the app. You enable the YouTube Data API v3, through the APIs and Services route. You create the OAuth consent screen, and add a test user under Audience and Test users, with the note that this can be any Google account but it should correspond with the YouTube account where videos will be uploaded.
Then you create credentials, selecting OAuth client ID and then Web application, and you add an authorised redirect URI of http://localhost:8080/oauth2callback. The client secrets JSON file is downloaded and saved as client_secrets.json in the same directory as the utility, with the expected shape given as an example: a web object containing the client_id, project_id, auth_uri, token_uri, auth_provider_x509_cert_url, client_secret and redirect_uris.
That redirect URI is not a default, it is the port the tool listens on when it asks for the token, and it is configurable through the -oAuthPort flag, whose default is 8080. If you set a different port you must set a matching redirect URI in the console, or the browser will come back to a page the tool is not serving and you will get an authorisation error with no obvious cause.
The tool uses the pkg/browser package to open the consent flow, so the first authorisation happens on a machine with a browser. The documentation then explains the headless arrangement: generate the token file locally first, then simply copy the token file along with youtubeuploader and client_secrets.json to the remote host. The token is cached in a file called request.token in the local directory, and the -cache flag changes that path.
The first run pops up a browser and prompts for YouTube credentials, and a token is created and stored for subsequent use.
Metadata comes from flags, from a JSON file, or from stdin
The metadata model is the part of the tool that a pipeline actually interacts with, and it offers two routes with a defined precedence.
The simplest invocation specifies a filename and nothing else:
./youtubeuploader -filename blob.mp4Beyond that, the flags cover the usual video fields: -title, -description with a default of uploaded by youtubeuploader, -tags as a comma separated list, -categoryId for the video category, -language defaulting to en, -privacy defaulting to private, -caption for a caption filename, -thumbnail for a thumbnail filename, and -playlistID to add the video to a playlist, which can be used multiple times. Two of them are unusual. The -notify flag notifies channel subscribers of a new video and defaults to true, and it can be disabled with -notify=false. And -sendFilename sends the original file name to YouTube, also defaulting to true.
The richer route is the -metaJSON flag, which takes a JSON file with the full metadata object. The example in the documentation includes title, description, tags, privacyStatus, madeForKids, embeddable, license, publicStatsViewable, publishAt, categoryId, recordingDate, playlistIds, playlistTitles, language, a localizations object mapping language codes to title and description, and containsSyntheticMedia.
The rules are stated plainly: all fields are optional, you use \n in the description to insert newlines, times can be provided in one of two formats, either yyyy-mm-dd in UTC or yyyy-mm-ddThh:mm:ss with a zone offset, and any values supplied via -metaJSON take precedence over flags.
That last rule settles the design question. You can drive everything from flags for a one-off, or from a JSON file for a pipeline, and you never have to decide which wins because the JSON file always does. The -metaJSONout flag writes the uploaded video metadata into a file, which is how a pipeline learns the YouTube video ID of what it just uploaded, and the -recordingDate flag takes a value such as 2024-11-23.
A URL filename streams through the local machine
There is one behaviour in the tool that has a direct operational consequence and is easy to miss, because it appears in two separate places in the documentation.
The -filename flag accepts a URL as well as a local path, and the -caption and -thumbnail flags say the same thing. When you specify a URL as the filename, the data will be streamed through the localhost, download from the remote host, then upload to YouTube. Reading that carefully: the file is fetched to the machine running the tool and then sent on.
That means a URL does not mean the upload streams directly from the remote host to YouTube. The tool is the intermediary in both directions. For a large file on a metered link, the download is charged to you and the upload is charged to you, and the file passes through the local disk or memory either way. For a file that is not public, a URL will not work at all, since there is nothing to fetch.
The related facility is the -filename flag accepting a single dash to read from stdin, which is the natural way to feed a file that a render pipeline has just produced and does not want to write to disk twice. A dash in the filename position reads the video from standard input.
There is also a -quiet flag to suppress the progress indicator, and the documentation explains how to still get progress output: send the signal USR1 to the process, for example kill -USR1 <pid>, on Linux and Unix only. The repository has a signal.go and a signal_windows.go, which is the expected shape for a feature that is not available on one platform, and it means the progress reporting is a nicety rather than a core feature on Windows.
The Go implementation is thin, and the dependency list explains the shape
The repository is small and the file names at the top level describe the whole thing: files.go, http.go, oauth.go, run.go, signal.go, signal_windows.go, a cmd directory, an internal directory and a test directory. A .goreleaser.yaml is present, which is what produces the precompiled binaries for Linux, Mac and Windows that the README tells you to download.
The go.mod file is short and its four direct dependencies tell you exactly what the tool does. google.golang.org/api at 0.238.0 is the generated Google API client, which is where the YouTube Data API types and the resumable upload machinery live. golang.org/x/oauth2 at 0.30.0 handles the token exchange. github.com/pkg/browser opens the consent window. And golang.org/x/time provides the rate limiting.
Everything else is indirect, and the indirect list tells you what the Google client pulls in. There is a long tail of cloud.google.com/go packages, the OpenTelemetry instrumentation including the auto SDK and the http otelhttp handler, gRPC, protobuf, and the standard x/crypto, x/net, x/sys and x/text modules. The OpenTelemetry entries in particular are a signal that the client emits traces, which is useful in a pipeline but also means the binary has a considerably larger dependency surface than the four direct requirements suggest.
The module requires Go 1.23.0 with a toolchain directive of go1.24.4, and the README carries a badge for a Go test workflow. The project is Apache-2.0 licensed.
The credit section is also worth reading, because it says where the design comes from. The tool is based on the Go YouTube API sample code, and the documentation thanks github.com/tokland/youtube-upload for insight into how to update playlists, which is where the -playlistID and -playlistTitles behaviour comes from.
Where youtubeuploader is the wrong tool
Three situations rule it out, and the first one is the one Google imposes on you.
The first is a channel that needs public videos immediately. The documentation notes that Google applies private status on videos uploaded to newly created projects, and it quotes the announcement directly: all videos uploaded via the videos.insert endpoint from unverified API projects created after 28 July 2020 will be restricted to private viewing mode, and to lift this restriction each project must undergo an audit to verify compliance with the Terms of Service. The tool defaults to private anyway, but the point is broader than the default: until the project is audited, that is the ceiling, not the starting point.
The second is a channel that uploads more than a handful of videos a day. The quota of about six videos per 24 hours is the constraint, and while the tool's rate limiting helps you work within it, it does not raise it. A channel publishing a daily show or a course with many short lessons is above the ceiling, and the documented answer is more authorised projects, not a better client.
The third is anything that needs to be more than an upload. The tool does one upload and exits. There is no media library, no channel analytics, no comment handling, no playlist management beyond adding the video to one, no scheduling beyond the rate limit window, and no queue. The repository description says scripted uploads to youtube, and the release note in the README about the tool being credited for insight into how to update playlists is about a narrow behaviour, not a management API.
So the sensible mental model is a step in a pipeline that renders or fetches a video and then hands it to this binary, rather than a channel management system. Where that pipeline needs the video ID of what it just uploaded, the -metaJSONout flag is the mechanism, and the recording date and publishAt fields are what let you schedule a batch rather than uploading everything at once.
Editorial conclusion
youtubeuploader fits a team with a build or render pipeline that produces videos on a schedule and needs to push them to a channel without a person opening a browser, and it fits a creator who wants to stay inside the YouTube upload quota while uploading a backlog. It is the wrong choice for a channel that needs public visibility on day one, because uploads from an unverified Google API project are forced to private status until an audit is passed, and it is the wrong choice if you need a media library, an analytics pipeline or a scheduler, since it does one upload and exits. Before adopting it, read the two notes in the documentation about private status and the daily quota, and create the OAuth client with the redirect URI the tool expects, http://localhost:8080/oauth2callback, because a mismatch there fails at the first authorisation rather than at the upload.
Frequently asked questions
What is youtubeuploader and what does it do?
It is a Go command line tool for scripted uploads to YouTube through the YouTube Data API v3. It uploads video files from local disk or from the web, and it can rate limit the upload bandwidth, with an option to rate limit only between given times of day.
How do I set up youtubeuploader for the YouTube API?
You create a project in the Google Developers Console, enable the YouTube Data API v3, set up an OAuth consent screen with a test user, and create an OAuth client ID of type Web application with an authorised redirect URI of http://localhost:8080/oauth2callback. The downloaded client secrets file is saved as client_secrets.json next to the utility.
How do I upload a video with youtubeuploader?
At a minimum, specify a filename with ./youtubeuploader -filename blob.mp4. The first run opens a browser for OAuth and stores a token in request.token in the local directory, so for a headless server you generate the token locally and copy the token file, the binary and client_secrets.json to the remote host.
What metadata can youtubeuploader set on a video?
Flags cover title, description, tags, category, language, privacy, caption, thumbnail and playlist, while the -metaJSON flag takes a JSON file with title, description, tags, privacyStatus, madeForKids, embeddable, license, publicStatsViewable, publishAt, recordingDate, playlistIds, playlistTitles, language and localizations. Values supplied via -metaJSON take precedence over flags.
Are there limits when uploading with youtubeuploader?
Yes. By default you can upload only about six videos every 24 hours due to Google quota restrictions, and videos uploaded to newly created unverified API projects are restricted to private viewing mode until the project passes a compliance audit.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/porjo-youtubeuploader)Was this useful?