I’m not a big fan of Youtube’s web application. I’d like to categorise my subscriptions, but YouTube doesn’t allow this. I have multiple Google accounts, and I use them to group certain videos. When I’m in the mode of watching software development videos, I switch to one account. If I’m in the LEGO mood, I switch to another. The problem is I’m not watching YouTube on Kodi on Raspberry Pi (about which I wrote a tutorial), and managing multiple accounts is harder, so I decided to combine all my subscriptions in one account. This article shows I managed to do it.
Authentication
First, Google needs to know you have permission to fetch the subscriptions from your YouTube account. To achieve this, go to Google Cloud Console.
Create a new project:

Then, click Library on the left menu and search for YouTube. Select YouTube Data API v3 and click Enable.

Click the OAuth consent screen link. Select External user type and click the Create button.

Give your app a name and select your account’s email as “User support email”. The app name appears on your confirmation screen so I’d recommend giving it a meaningful name such as YouTube-Migration-Source (there will be a destination too).
Enter your email again as “Developer contact information”.
Click Save and Continue.
In the Scopes screen, click Add or Remove Scopes and select youtube:

Click Save and Continue.
Adding Test users is not mandatory but is helpful in the next steps so I’d recommend adding your email address as a test user. This way you can still use the application without having to publish it.
Click Save and Continue after you’ve added your email address as a test user.
Then, click the Credentials link on the menu.
Click Create Credentials and select OAuth client ID.
Select Desktop app as your application type, give it a name and click the Create button.

Click Download JSON in the confirmation dialog box:

Now, the good news is that you have the credentials to access your source YouTube account. The bad news is you have to repeat the same steps for the destination account. In the end, rename your credential files to client_secrets_source.json and client_secrets_destination.json and move on to the next section to implement the application.
Implement the Application
Now that the boring part is over let’s write some code and have fun.
Create a new dotnet console project by running
mkdir YouTubeMigrationClient cd YouTubeMigrationClient dotnet new console
Then add Google YouTube SDK to the project:
dotnet add package Google.Apis.YouTube.v3
Copy client_secrets_source.json and client_secrets_destination.json files under this project’s folder.
Add a new C# file named YouTubeServiceFactory.cs and replace its contents with the code below:
using System.Reflection; using Google.Apis.Auth.OAuth2; using Google.Apis.Util.Store; using Google.Apis.YouTube.v3; namespace YouTubeMigrationClient; public class YouTubeServiceFactory { public static async Task<UserCredential> CreateCredential(string credentialFileName) { UserCredential credential; using (var stream = new FileStream(credentialFileName, FileMode.Open, FileAccess.Read)) { credential = await GoogleWebAuthorizationBroker.AuthorizeAsync( (await GoogleClientSecrets.FromStreamAsync(stream)).Secrets, new[] { YouTubeService.Scope.Youtube }, "user", CancellationToken.None, new FileDataStore(Assembly.GetExecutingAssembly().GetType().ToString()) ); } return credential; } }
To test the credentials, edit the Program.cs and replace the code with this:
using System.Reflection; using Google.Apis.Services; using Google.Apis.YouTube.v3; using YouTubeMigrationClient; var sourceYouTubeCredential = await YouTubeServiceFactory.CreateCredential("client_secrets_source.json"); var sourceYouTubeService = new YouTubeService(new BaseClientService.Initializer() { HttpClientInitializer = sourceYouTubeCredential, ApplicationName = Assembly.GetExecutingAssembly().GetType().ToString() }); var sourceSubscriptionListRequest = sourceYouTubeService.Subscriptions.List("id,snippet"); sourceSubscriptionListRequest.Mine = true; var sourceSubscriptions = await sourceSubscriptionListRequest.ExecuteAsync(); foreach (var subscription in sourceSubscriptions.Items) { Console.WriteLine($"ChannelId: {subscription.Snippet.ResourceId.ChannelId}\t\tTitle: {subscription.Snippet.Title}"); } await sourceYouTubeCredential.RevokeTokenAsync(new CancellationToken());
The reason we’re revoking the token is to be able to authenticate to both source and destination accounts. If we don’t revoke it now, it still tries to use the source account’s access token when we try to access the destination account. It will be more obvious when you’ve finished implementing the application.
Now run the application in your terminal:
dotnet run
It should launch your default browser and show a Google account selection screen:

As this is an application in testing on Google’s side, it shows a warning:

Click Continue.
Then it asks for your permission to allow the app to access your Google Account.

Click Allow.
After the authorization is complete, you will see a message that you can close the tab. Do so and go back to your terminal window. You should now see up to 5 (default page size) results like this:

I’d recommend running the application again with client_secrets_destination.json to confirm they both work. This is where revoking the token helps because otherwise, you wouldn’t be asked to select an account.
Refactor Getting Source Subscriptions
The default page size is 5. You can adjust this by setting MaxResults as shown below:
sourceSubscriptionListRequest.MaxResults = 10;
Unfortunately, the maximum value allowed is 50. If you have more than 50 subscriptions, your implementation won’t be able to migrate all of them, which is something you need to fix.
Google uses paging in their results, and you can access the previous and next pages by setting the PageToken property to one of those tokens. The refactored version below shows how it works:
string nextPageToken = null; var sourceSubscriptionList = new List<Subscription>(); do { var sourceSubscriptionListRequest = sourceYouTubeService.Subscriptions.List("id,snippet"); sourceSubscriptionListRequest.Mine = true; sourceSubscriptionListRequest.MaxResults = 10; sourceSubscriptionListRequest.Order = SubscriptionsResource.ListRequest.OrderEnum.Alphabetical; sourceSubscriptionListRequest.PageToken = nextPageToken; var sourceSubscriptions = await sourceSubscriptionListRequest.ExecuteAsync(); nextPageToken = sourceSubscriptions.NextPageToken; sourceSubscriptionList.AddRange(sourceSubscriptions.Items); Console.WriteLine(sourceSubscriptions.Items.Count); } while (nextPageToken != null); Console.WriteLine(sourceSubscriptionList.Count);
The example gets results 10 at a time and keeps doing it as long as NextPageToken is not null.

So now you have access to your entire subscription list, let’s talk about migrating them into the destination account.
Import Subscriptions into the Destination Account
The next step is to iterate over the subscription list and add them to the destination account. Add the following code block to Program.cs:
var targetYouTubeCredential = await YouTubeServiceFactory.CreateCredential("client_secrets_destination.json"); var targetYouTubeService = new YouTubeService(new BaseClientService.Initializer() { HttpClientInitializer = targetYouTubeCredential, ApplicationName = Assembly.GetExecutingAssembly().GetType().ToString() }); foreach (var subscription in sourceSubscriptionList) { Console.WriteLine($"ChannelId: {subscription.Snippet.ResourceId.ChannelId}\t\tTitle: {subscription.Snippet.Title}"); var targetSubscription = new Subscription { Snippet = new SubscriptionSnippet { ResourceId = new ResourceId { Kind = "youtube#subscription", ChannelId = subscription.Snippet.ResourceId.ChannelId } } }; try { await targetYouTubeService.Subscriptions.Insert(targetSubscription, "id,snippet").ExecuteAsync(); } catch (Exception e) { Console.WriteLine(e.Message); } } await targetYouTubeCredential.RevokeTokenAsync(new CancellationToken());
The exception handling is to ensure the program keeps running if you already have the same subscription in the destination account.
Run the application again, and you should see it adding the subscriptions one by one. After you’re done, refresh your destination account and confirm the results.
Here’s the final version of the Program.cs in case you didn’t follow along:
using System.Reflection; using Google.Apis.Services; using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3.Data; using YouTubeMigrationClient; // Get the subscriptions from the Source Account var sourceYouTubeCredential = await YouTubeServiceFactory.CreateCredential("client_secrets_source.json"); var sourceYouTubeService = new YouTubeService(new BaseClientService.Initializer() { HttpClientInitializer = sourceYouTubeCredential, ApplicationName = Assembly.GetExecutingAssembly().GetType().ToString() }); string nextPageToken = null; var sourceSubscriptionList = new List<Subscription>(); do { var sourceSubscriptionListRequest = sourceYouTubeService.Subscriptions.List("id,snippet"); sourceSubscriptionListRequest.Mine = true; sourceSubscriptionListRequest.MaxResults = 50; sourceSubscriptionListRequest.Order = SubscriptionsResource.ListRequest.OrderEnum.Alphabetical; sourceSubscriptionListRequest.PageToken = nextPageToken; var sourceSubscriptions = await sourceSubscriptionListRequest.ExecuteAsync(); nextPageToken = sourceSubscriptions.NextPageToken; sourceSubscriptionList.AddRange(sourceSubscriptions.Items); } while (nextPageToken != null); Console.WriteLine($"Retrieved {sourceSubscriptionList.Count} subscriptions from the source account"); await sourceYouTubeCredential.RevokeTokenAsync(new CancellationToken()); // Import subscriptions into the Destination Account var targetYouTubeCredential = await YouTubeServiceFactory.CreateCredential("client_secrets_destination.json"); var targetYouTubeService = new YouTubeService(new BaseClientService.Initializer() { HttpClientInitializer = targetYouTubeCredential, ApplicationName = Assembly.GetExecutingAssembly().GetType().ToString() }); foreach (var subscription in sourceSubscriptionList) { Console.WriteLine($"ChannelId: {subscription.Snippet.ResourceId.ChannelId}\t\tTitle: {subscription.Snippet.Title}"); var targetSubscription = new Subscription { Snippet = new SubscriptionSnippet { ResourceId = new ResourceId { Kind = "youtube#subscription", ChannelId = subscription.Snippet.ResourceId.ChannelId } } }; try { await targetYouTubeService.Subscriptions.Insert(targetSubscription, "id,snippet").ExecuteAsync(); } catch (Exception e) { Console.WriteLine(e.Message); } } await targetYouTubeCredential.RevokeTokenAsync(new CancellationToken());
Conclusion
This is a simple tutorial about managing your Youtube account by using your own code. Other tools do the same job, but nothing beats the experience and satisfaction of achieving something by software that you built yourself. I hope you enjoyed it too.