Cover image
9 minute read

YouTube API Integration: Uploading Videos with Django

Uploading a video to YouTube seems like a simple enough task, but non-existent documentation and unmaintained libraries can make even the simplest of tasks seem daunting. In this post, Toptal Freelance Django Developer Ivan Carmo da Rocha Neto guides you through how to post YouTube videos from your Django app.

A short while ago, I was working for a client, integrating video reviews in their website. Like any motivated developer solving a novel problem, the first thing I did was Google it, and I found a plethora of unhelpful or misguided answers on how to achieve something entirely different, or outdated and unmaintained Python packages. Eventually, I bit the bullet and the team and I built everything from scratch: we created the views, learned about Google’s API, created the API client, and eventually succeeded in programmatically uploading videos from Django.

In this post, I’ll try to guide you step by step in how to post YouTube videos from your Django app. This will require a bit of playing around with Google API credentials—first with the web interface, then with the code. The YouTube part itself is very straightforward. We need to understand how Google stuff works because sometimes it’s tricky and the information is spread through many places.


I recommend familiarizing yourself with the following before we begin working:

An interesting bit of code to note is the following Python Snippet from the Google YouTube API Docs:

# Sample python code for videos.insert
def videos_insert(client, properties, media_file, **kwargs):
  resource = build_resource(properties) # See full sample for function
  kwargs = remove_empty_kwargs(**kwargs) # See full sample for function
  request = client.videos().insert(
    media_body=MediaFileUpload(media_file, chunksize=-1,

  # See full sample for function
  return resumable_upload(request, 'video', 'insert')

media_file = 'sample_video.flv'
  if not os.path.exists(media_file):
    exit('Please specify a valid file location.')
    {'snippet.categoryId': '22',
     'snippet.defaultLanguage': '',
     'snippet.description': 'Description of uploaded video.',
     'snippet.tags[]': '',
     'snippet.title': 'Test video upload',
     'status.embeddable': '',
     'status.license': '',
     'status.privacyStatus': 'private',
     'status.publicStatsViewable': ''},

Getting Started

After you’ve read the prerequisites, it’s time to get started. Let’s see what we need.


Basically, let’s create a virtual environment. I personally prefer pyenv. Setting up both is out of the scope of this post, so I’m going to post some pyenv commands below and, if your preference is virtualenv, feel free to replace the commands accordingly.

I’m going to use Python 3.7 and Django 2.1 in this post.

➜  ~/projects $ mkdir django-youtube
➜  ~/projects $ cd django-youtube
➜  ~/projects/django-youtube $ pyenv virtualenv 3.7.0 djangoyt

➜  ~/projects/django-youtube $ vim .python-version

Let’s put this in the contents (just if you use pyenv, so it activates automatically when you enter the folder):


Installing dependencies:

➜  ~/projects/django-youtube $ pip install google-api-python-client google-auth\
 google-auth-oauthlib google-auth-httplib2 oauth2client Django unipath jsonpickle

Now time to start our django project:

➜  ~/projects/django-youtube $ django-admin startproject django_youtube .

Pause for some Google Config

Let’s config our project credentials now so we are able to use the Google APIs.

Step 1. Go to the following URL:

Step 2. Create a new project.

Create a new project

Step 3. Click “Enable APIs and Services.”

Enable APIs and Services.

Step 4. Look for YouTube Data API v3, and click “Enable.”

Look for YouTube Data API v3, and click "Enable."

Step 5. You should get a message about credentials.

a message about credentials

Step 6. Click on the “Create credentials” blue button on the right side, and you should get the following screen:

Click on the "Create credentials" blue button

Step 7. Choose Web server, User Data:

Choose Web server, User Data

Step 8. Add authorized JS origins and redirect URIs. Continue to the end:

Add authorized JS origins and redirect URIs.

OK we are done with our credentials set up. You can either download the credentials in a JSON format or copy the Client ID and Client Secret.

Back to Django

Let’s start our very first Django app. I usually name it “core”:

(djangoyt) ➜  ~/projects/django-youtube $ python startapp core

Now, let’s add the following to our root file to route the homepage requests to our core app:

# <root>/

from django.urls import path, include

    path('', include(('core.urls', 'core'), namespace='core')),

In the core app, let’s have another file, with some config also:

# core/

from django.conf import settings
from django.conf.urls.static import static
from django.urls import path

from .views import HomePageView

urlpatterns = [
    path('', HomePageView.as_view(), name='home')

if settings.DEBUG:
    urlpatterns += static(
        settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(
        settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

See there is an empty path pointing to HomePageView. Time to add some code.

Let’s do now a simple TemplateView just to see it running.

# core/
from django.shortcuts import render
from django.views.generic import TemplateView

class HomePageView(TemplateView):
    template_name = 'core/home.html'

And of course we need a basic template:

# core/templates/core/home.html
<!DOCTYPE html>

<h1>My First Heading</h1>
<p>My first paragraph.</p>


We need to do some settings tweaks:

from unipath import Path

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = Path(__file__).parent


STATIC_ROOT = BASE_DIR.parent.child('staticfiles')

STATIC_URL = '/static/'

MEDIA_ROOT = BASE_DIR.parent.child('uploads')

MEDIA_URL = '/media/'

Let’s create now a YoutubeForm and add it as form_class to the view:

# core/
from django import forms
from django.views.generic.edit import FormView

class YouTubeForm(forms.Form):

class HomePageView(FormView):
    template_name = 'core/home.html'
    form_class = YouTubeForm

Try to run your application now, and the page will look like this:

Page preview

Pause to Do Authorization

First of all, you have to create a model to store your credentials. You could to through a file, cache system, or any other storage solution, but a database seems reasonable and scalable, and also you can store credentials per users if you want.

Before proceeding, an adjustment needs to be made—there is fork of oauth2client that supports Django 2.1 that we have to use. Soon, we’ll have official support, but in the meantime, you can inspect the fork changes. They are very simple.

pip install -e git://
Because of compatibility with Django 2.1

Go to your and place the Client ID and Client Secret you got from Google in previous steps.


GOOGLE_OAUTH2_CLIENT_ID = '<your client id>'
GOOGLE_OAUTH2_CLIENT_SECRET = '<your client secret>'

Caution: storing secrets in your code is not recommended. I’m doing this simply as a demonstration. I recommend using environment variables in your production app, and not hardcoding secrets in application files. Alternatively, if you downloaded the JSON from Google, you can also specify its path instead of the settings above:

GOOGLE_OAUTH2_CLIENT_SECRETS_JSON = '/path/to/client_id.json'

The oauth2client package already provides plenty of functionality, with a CredentialsField already done that we can use. It’s possible to add more fields, like a foreign key and created/modified dates so we get more robust, but let’s stay simple.

Simple model to store credentials:

# core/
from django.db import models
from oauth2client.contrib.django_util.models import CredentialsField

class CredentialsModel(models.Model):
    credential = CredentialsField()

Time to create migrations and migrate:

(djangoyt) ➜  ~/projects/django-youtube $ ./ makemigrations core

(djangoyt) ➜  ~/projects/django-youtube $ ./ migrate

Now let’s change our API views to be able to authorize our application:

In our core/ file, let’s add another entry for the first authorization view:

# core/
from .views import AuthorizeView, HomePageView

urlpatterns = [
    # [...]
    path('authorize/', AuthorizeView.as_view(), name='authorize'),

So, the first part of the AuthorizeView will be:

# core/

from django.conf import settings
from django.shortcuts import render, redirect
from django.views.generic.base import View

from oauth2client.client import flow_from_clientsecrets, OAuth2WebServerFlow
from oauth2client.contrib import xsrfutil
from import DjangoORMStorage
from .models import CredentialsModel

# [...]

class AuthorizeView(View):

    def get(self, request, *args, **kwargs):
        storage = DjangoORMStorage(
            CredentialsModel, 'id',, 'credential')
        credential = storage.get()
        flow = OAuth2WebServerFlow(

        # or if you downloaded the client_secrets file
        '''flow = flow_from_clientsecrets(

And then the second part:

        if credential is None or credential.invalid == True:
            flow.params['state'] = xsrfutil.generate_token(
                settings.SECRET_KEY, request.user)
            authorize_url = flow.step1_get_authorize_url()
            return redirect(authorize_url)
        return redirect('/')

So if there is no credential or the credential is invalid, generate one and then redirect it to the authorize URL. Otherwise, just go to the homepage so we can upload a video!

Let’s access the view now and see what happens:

Authorization error

Let’s create a user then, before going to that page.

(djangoyt) ➜  ~/projects/django-youtube $ python createsuperuser

Username (leave blank to use 'ivan'): ivan
Email address: ivan***
Password (again):
This password is too short. It must contain at least 8 characters.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

Let’s also log in with it via /admin. After, let’s access our /authorize/ view again.

Let's login



404 Error

OK, it tried to redirect to the callback URL we configured long ago with Google. Now we need to implement the callback view.

Let’s add one more entry to our core/

# core/

from .views import AuthorizeView, HomePageView, Oauth2CallbackView

urlpatterns = [
    # [...]
    path('oauth2callback/', Oauth2CallbackView.as_view(),

And another view:

# core/

# the following variable stays as global for now
flow = OAuth2WebServerFlow(
# or if you downloaded the client_secrets file
'''flow = flow_from_clientsecrets(

# [...]

class Oauth2CallbackView(View):

    def get(self, request, *args, **kwargs):
        if not xsrfutil.validate_token(
            settings.SECRET_KEY, request.GET.get('state').encode(),
                return HttpResponseBadRequest()
        credential = flow.step2_exchange(request.GET)
        storage = DjangoORMStorage(
            CredentialsModel, 'id',, 'credential')
        return redirect('/')

Note: The flow was moved to outside of the AuthorizeView, becoming global. Ideally, you should create it under the AuthorizeView and save in a cache, then retrieve it in the callback. But that is out of the scope of this post.

The get method of AuthorizeView is now:

    def get(self, request, *args, **kwargs):
        storage = DjangoORMStorage(
            CredentialsModel, 'id',, 'credential')
        credential = storage.get()

        if credential is None or credential.invalid == True:
            flow.params['state'] = xsrfutil.generate_token(
                settings.SECRET_KEY, request.user)
            authorize_url = flow.step1_get_authorize_url()
            return redirect(authorize_url)
        return redirect('/')

You can take a look at similar implementations here. The oauth2client package itself provides views but I particularly prefer to implement my custom Oauth view.

Now if you try the /authorize/ URL again, the OAuth flow should work. Time to see if this work is worth it and upload our video! The HomePageView will first check for credentials and if it’s all good, we are ready for uploading our video.

Let’s check how our new code for the HomePageView will look:

import tempfile
from django.http import HttpResponse, HttpResponseBadRequest
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload

class HomePageView(FormView):
    template_name = 'core/home.html'
    form_class = YouTubeForm

    def form_valid(self, form):
        fname = form.cleaned_data['video'].temporary_file_path()

        storage = DjangoORMStorage(
            CredentialsModel, 'id',, 'credential')
        credentials = storage.get()

        client = build('youtube', 'v3', credentials=credentials)

        body = {
            'snippet': {
                'title': 'My Django Youtube Video',
                'description': 'My Django Youtube Video Description',
                'tags': 'django,howto,video,api',
                'categoryId': '27'
            'status': {
                'privacyStatus': 'unlisted'

        with tempfile.NamedTemporaryFile('wb', suffix='yt-django') as tmpfile:
            with open(fname, 'rb') as fileobj:
                insert_request = client.videos().insert(
              , chunksize=-1, resumable=True)

        return HttpResponse('It worked!')

And the new template:

{# core/templates/core/home.html #}

        <!DOCTYPE html>

        <h1>Upload your video</h1>
        <p>Here is the form:</p>
        <form action="." method="post" enctype="multipart/form-data">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Submit">


Don’t forget to add the video field to YouTubeForm:

class YouTubeForm(forms.Form):
    video = forms.FileField()

Here we go!

Upload form

And then, checking at your YouTube account Studio page (it’s important to have a channel):

Uploaded video


Closing Notes

The code needs some improvement, but it’s a good starting point. I hope it helped with most of the Google’s YouTube API Integration problems. Here are a few more important things to note:

  • For authorization, it’s important to require login and extra permissions for the user that will authorize your application to be uploading videos.
  • The flow variable needs to be moved out from being global. It isn’t safe in a production environment. It’s better to cache based on the user ID or session who accessed the first view, for instance.
  • Google only provides a refresh token when you do the first authorization. So after some time, mostly one hour, your token will expire and if you didn’t interact with their API you will start receiving invalid_grant responses. Reauthorizing the same user who already authorized a client will not guarantee your refresh token. You have to revoke the application in your Google Accounts page and then do the authorization process again. In some cases, you might need to run a task to keep refreshing the token.
  • We need to require login in our view since we are using a user credential directly related to the request.

FlowExchange Error

Uploading takes a lot of time, and doing it in your main application process can cause the entire application to block while the upload happens. The right way would be to move it into its own process and handle uploads asynchronously.

Confused? Don’t be, read more in Orchestrating a Background Job Workflow in Celery for Python.

Understanding the basics

From the official website: “Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design”. It’s the most biggest Python Web Framework and it’s widely used because it offers tools for fast web development, including templating, database handling and security.

Definitely. It’s the most popular and is one of the top frameworks. Django takes care of most of the common web tasks so you can focus on your application. If you give three choice options for a Python developer when starting a web project, Django will certainly be one.

It’s an API provided by Google to include all kinds of YouTube functionality to a given site. This includes uploading of videos, management of playlists and subscriptions, handling of channel settings, search, and others.

The very first step would be to go here (, but that will direct you to the Google Developers Console ( There you can search for Youtube API and then add it.

Some Google APIs are free, others are not. The Youtube Data API, for instance, is free.

In order to follow this post strictly, you need Python 3.7 and Django 2.1, but this should work with Python 2.7, 3.4, 3.5, 3.6 and Django 1.1, and 2.0 as well.

You need a YouTube account and at least one channel.