I do love Instagram and the cool ways we can connect through with, see other cities, people we miss. It’s just hard to deal with having my photos kind of trapped there.

It is possible to export them, but that process takes a while. So this last weekend I took some time to improve a script that I used to extract my photos from there and onto this site.

They are in the pages of the Photos section.

Here is a step by step on what I did for those more tech-inclined.

Some things to take into account before moving on:

  1. We are assuming the website is ran by Hugo.
  2. You need some knowledge in using the Command Line of your computer.
  3. Some of the information below includes what is useful for me, your needs will vary.


Instaloader is a tool to download pictures (or videos) along with their captions and other metadata from Instagram.

This command line tool does the heavy lifting, it authenticates you to Instagram, queries your profile and posts and saves everything to a nice file structure.

What we are going to do is take those files, extract data from them, and save it as environment variables. Those will then be picked up by the Hugo Archetype we defined for new posts in that section.

Archetype for Instagram Posts

When you create new content, Hugo uses a template for that file. That template is called an Archetype.

There are some Hugo functions you can use in these templates, and we are using getenv that allows us to use environment variables in your new post.

Take a look:

date: {{ getenv "post_datetime"}}
draft: false
- src: {{ getenv "post_image" }}
  name: "header"
layout: instagram
  - link: {{ getenv "google_maps_link"}}
  - latitude: {{ getenv "latitude" }}
  - longitude: {{ getenv "longitude" }}
title: "{{ getenv "post_title" }}"
tags: {{ getenv "post_tags"}}

{{ getenv "post_content" }}

You can tweak this file to add more information, as long as you also do the same in the script that puts it all together. More on that later.

Import and update script

The script became a little more complex than what was to be expected. To keep things in order, it’s hosted on GitHub.


  1. Edit the config section of the script
  2. Run the setup with ./instagram-posts-from-instaloader.sh setup
  3. Execute the update ./instagram-posts-from-instaloader.sh run-update

Running setup, you will be asked for your username and password. Instaloader will setup a directory with your username where it will store your photos alongside the metadata it found.

We are keeping this directory untouched, read from it, and save the information we need as an environment variable. The full code is below and on GitHub.

Inside the update-post function we are reading the json data with xzcat and jq. If you want to add more data to the exported post, this is where you should look.

After this is done, we check if the post exists and if not, run hugo new ... and copy any jpg or video files that may be included.

With this done, your new posts should be ready. Run hugo server to make sure it’s working as you expected.

If you wish to add or improve any bit of the script, feel free to submit a merge request on the GitHub page.

Let me know in the comments if this was useful. 😀


### 1. Run the setup with `./instagram-posts-from-instaloader.sh setup`
### 2. Execute the update `./instagram-posts-from-instaloader.sh run-update`

## Config

### Where is your blog located?
### What is your Instagram Username?
### Where are we saving the output from Instaloader? (do not include the username)

### Where do you want photos saved to? Relative to ./content 

# Dependency check

command -v instaloader >/dev/null 2>&1 || { echo >&2 "I require Instaloader but it's not installed. Aborting."; exit 1 }
command -v xzcat >/dev/null 2>&1 || { echo >&2 "I require xzcat but it's not installed. Aborting."; exit 1 }
command -v jq >/dev/null 2>&1 || { echo >&2 "I require jq but it's not installed. Aborting."; exit 1 }
command -v hugo >/dev/null 2>&1 || { echo >&2 "I require hugo but it's not installed. Aborting."; exit 1 }

setup() {
  cd $directoryWithInstagramPosts; 
  instaloader --fast-update --geotags --login=$instagramUser $instagramUser

  find "$directoryWithInstagramPosts/$instagramUser" -maxdepth 1 -name '*.json.xz'  -exec ./instagram-posts-from-instaloader.sh update-post {} \;
}  >&2

update-post() {
  file=$(basename $1)
  dir=$(dirname $1)
  content=`xzcat "$1" | jq '.node.edge_media_to_caption.edges[0].node.text'`

  declare -a tags;
  tags=$(echo $content | grep -o '#[[:alpha:]|[:alnum:]]*')
  jsontags=$(printf '%s' "${tags[@]}" | jq -R . | jq -s .)
  jsontags=${jsontags//[$'\t\r\n'|$' ']}
  date=`cut -d'_' -f 1 <<< "$file"`
  time=`cut -d'_' -f 2 <<< "$file"`

  export post_image=$imagefinal;
  export post_datetime=$(echo $date"T"${time//-/:}"+00:00")
  export post_tags=$jsontags
  if [[ $content = null ]]; then
    export post_slug
    export post_title=$date
    export post_content=" "
    slug=$(echo "$content" |  iconv -c -t ascii//TRANSLIT | sed -E 's/[~\^]+//g' | sed -E 's/[^a-zA-Z0-9]+/-/g' | sed -E 's/^-+\|-+$//g' | sed -E 's/-$//g' | tr A-Z a-z )
    export post_slug=${slug:0:25}
    export post_title=${title//'"'}
    export post_content=$content



  location=$(grep -s 'maps.google.com' $grepLocation)
  if [[ ! -z "$location"  ]]; then
  echo "found $location"
    export google_maps_link=$location 
    export latitude=$(cut -d'=' -f 2 <<< "$locationClean" | cut -d'\' -f 1 | cut -d',' -f 1)
    export longitude=$(cut -d'=' -f 2 <<< "$locationClean" | cut -d'\' -f 1 | cut -d',' -f 2)

  if [[ ! -f "$destination/index.md"  ]]; then
    hugo new $instagramdir/$date-$time$post_slug/index.md
    cp ${1:r:r}(*.jpg|*.mp4) $destination
    echo 'post exists: '$post_title

}  >&2

if [[ $1 = "setup" ]]; then

if [[ $1 = "run-update" ]]; then

if [[ $1 = "update-post" ]]; then
  update-post $2
Unsplash LogoLisa Fotios

avatar Bruno Amaral
Bruno Amaral

I am a Digital Strategist, divided between tech and creativity, working for the Lisbon Collective and teaching Public Relations at the …