First, I’d like to talk about why I wanted to include Bitly links.

  1. Despite the continued prevalence of social media buttons, there’s a decent wealth of data demonstrating they don’t work.
  2. I have no interest in providing free advertising for large, ethically bankrupt corporations whose policies I do not approve of.
  3. It’s kind of fun! For no charge (aside from the domain) one can get personalized shortened permalinks.

When I made the first version of this website with Jekyll there was a simple plugin that caught my eye that made it dead simple to add short URLs from the Bitly API. When I thought I was going to move my website over to Metalsmith I even wrote an integration plugin as one didn’t exist yet.

Ultimately, I landed on building an API with Django, but the plugins I found didn’t accomplish what I was hoping for. Django-bitly didn’t fit my needs as I was only using Django to serve an JSON, not HTML. Most related snippets and examples I found were for outdated versions of the Bitly API, outdated versions of Django, or both.

In order to use the solution I came up with you’ll need:

Include something like the following in your relevant model file.

from django.db import models
from dirtyfields import DirtyFieldsMixin
import requests
import os

class Post(DirtyFieldsMixin, models.Model):
    # you don't need to have your slug as your primary_key
    # but you do need a reference to whatever your path is
    # in addition to your root domain
    slug = models.SlugField(primary_key=True)
    shortlink = models.CharField(max_length=20, null=True, blank=True, default='')

    def set_shortlink(self):
        url = ""
        headers = {
            "Host": "",
            "Accept": "application/json",
            "Authorization": f"Bearer {os.environ.get('BITLY_TOKEN')}"
        payload = {
            "group_guid": os.environ.get('BITLY_GROUP_GUID'),
            "long_url": f"{self.slug}"
        # constructing this request took a good amount of guess
        # and check. thanks Postman!
        r =, headers=headers, json=payload)

        if r.status_code is 200:
            self.shortlink = r.json()[u'id']

    def save(self, *args, **kwargs):
        # if the short link doesn't have a value *or* the
        # slug that the short link is based off of has changed
        if not self.shortlink or 'slug' in self.get_dirty_fields():

            super().save(*args, **kwargs)

This example has been shortened for brevity, the full context can be found at this site’s repository.

Extra credit: learn about the benefits of environment variables aside from security.