I made a Chrome extension.

The guy that runs my program is always telling us that if you really want to learn something you should volunteer to give a talk or presentation on it, because at that point you have to learn it. I thought it’d be cool to see what goes into making a Chrome extension, so I did that. It wasn’t very good. I mean, my presentation was fine and showed how to set stuff up and I was charming and hilarious, sure, but the actual extension that we made was kinda dumb and useless (it turned the background of whatever page you were looking at bright red. It did not change it back.)

My cohort somehow managed to end up with a project-free weekend, though, so I gave myself three goals for Saturday:

  1. Make a slightly less useless extension than than my first one. I wasn’t trying to solve world hunger or anything, I just wanted to make something that I could maybe see myself using every now and then.
  2. No frameworks or libraries. I’ve been writing a lot of React and Redux lately (which is great, they’re both awesome), but I’ve also been getting a little crazy with the dependencies (shoutout Lodash), so I wanted to get back to the basics and make this with nothing but HTML, CSS, and good ole’ fashioned Vanilla JavaScript.
  3. Use Unsplash. I had been meaning to get around to finally using their API for something and this seemed like as good a time as any. If you’re unfamiliar with Unsplash, it’s an absolutely wonderful website that has a bunch of SUPER high-resolution and awesome photos that are also completely open source. It is probably my favorite place on the internet.

I ended up deciding to make a simple extension that figures out where you are, what the weather’s like there, and then sets the background of the extension to a picture related to whatever that weather is. So…let’s get started, yeah?

The most important part of a Chrome extension directory is the manifest.json file. This basically tells the browser what stuff to look for and where to look for it. The whole file uses JSON notation, so remember to make all the keys strings and add commas where appropriate.

(I’m going to show the code I wrote and then describe that code under each snippet. This is my first time writing something like this and I’m trying to figure out what works best. Thank you for your patience.)

"manifest_version": 2,

First thing we need to do is add a manifest version to let Chrome know that we’re using the newest and shiniest manifest file version.

"name": "Weathered",
"description": "An extension for seeing the local weather",
"version": "1.0",

Next up was some basic housekeeping stuff. So far, so good. Also kind of boring.

"browser_action": {
"default_title": "Click for weather!",
"default_popup": "popup.html"

Alright, so now we’re doing some actual stuff. The default_title is what shows up when you hover your mouse over the icon in your extension toolbar. I decided to make this extension display stuff in its own window when clicked instead of the background of a new tab, and the default_popup lets Chrome know to look at popup.html to see what it should display.

"permissions": [

These permissions just list the sites and APIs you want your extension to be allowed to use. We’ll talk a little more about this later, but I wasted LOTS of time trying to do stuff without these.

Cool, so now we have a manifest file. We already told our manifest that the main page is going to be something called popup.html, so let’s create that file now and add some very basic HTML.

<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css">
<section class="local-weather">
<h1 class="main-text"></h1>
<script src="popup.js"></script>

Boom, HTMLed. Notice that we’ve given this page a section with a class of “local-weather” (we’ll dynamically change the background for this later) and an h1 called “main-text” that is, you guessed it, the primary (only) text on our page. We’re also including “popup.css” for styling and “popup.js” for logic. Let’s do the styling next. If you haven’t already, add a file called “popup.css.”

@import url('https://fonts.googleapis.com/css?family=Lato');
.local-weather {
align-items: center;
background-size: cover;
display: flex;
height: 500px;
justify-content: center;
width: 500px;
.main-text {
background: rgba(255,255,255,0.5;
color: white;
font-family: 'Lato', sans-serif;
font-size: 3em;
font-weight: bold;
text-align: center;

There’s nothing too exciting happening here, but we can talk about it anyway. We’re bringing in a Google font (“Lato” — I’m not a typography buff, but I like this one) and then telling our two HTML elements how to look when we finally put stuff there.

Ok, now we’re going to start writing the logic that’ll make our extension actually do stuff. Add a popup.js file and then let’s think about how this is all going to work.

We’re going to use a weather word to search Unsplash for a picture, so we’ll need to get our weather, right? But we can’t get our weather without knowing our location, so the first thing we need to do is figure out where the person using this extension is with the geolocation API. One of the nice things about a project like this is that you don’t really have to worry about older browsers or cross-browser compatibility because, you know, it’s a Chrome extension. That means we also get access to lots of cutting edge JavaScript stuff without needing something like Babel to make everything backwards-compatible. So let’s do some geolocation.

(I’m going to be using ES6 here, so if you’re wondering where all the functions and variables are and what in the *&$# those arrows are, you can learn that here. I’m also going to be using the OpenWeatherMap and Unsplash APIs that you’ll need to sign up for if you want to make your own version.)

const getAndDisplayWeather = () => {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
//we need to do something with our position here//
else { console.log('No geolocation so no app frown face') }

So we’re checking to see if geolocation is available (it should be, we’re using Chrome, but I put an error message in there just in case.) That “position” will be a big object with lots of information, but we really only need the latitude and longitude to give to the Weather API. That’ll look something like this:

const getAndDisplayWeather = () => {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {

So we have our local coordinates and are giving them to this “getLocalWeather” function that we haven’t written yet. We should probably do that now. This is also where that “permissions” part of the manifest file becomes important. If we don’t tell Chrome to allow geolocation, this is going to blow up and then I’ll have to quit programming and move back to China probably (I’m constantly looking for an excuse to move back to China.)

Before we do, remember what I said about using Vanilla JS? Yeah, that means we’re gonna have to do some HTTP/API stuff without using jQuery or Axios or one of a million other things that people much smarter than me have made to make that sort of thing easier. I had never done HTTP requests with Vanilla JS before, so I used MDN to learn how. Let’s make a few variables that we’re going to be using in this getLocalWeather function.

const xhr = new XMLHttpRequest();
let weatherAPIKey = <My OpenWeatherMap API key>;
let mainText = document.querySelector('.main-text');
let localWeather = document.querySelector('.local-weather');

These are just going to save us some typing later on, basically. Now let’s get that weather.

const getLocalWeather = (latitude, longitude) => {
xhr.open("GET", `http://api.openweathermap.org/data/2.5/weather? lat=${latitude}&lon=${longitude}&appid=${weatherAPIKey}&units=imperial`, true);
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
const response = JSON.parse(xhr.responseText);
mainText.innerText = `Weather in ${response.name} be like...`
else { console.log('Error connecting to OpenWeather, your internet is make the broken')}

The first thing we do is define what type of request we’re making and where we’re making it (“GET” and the OpenWeatherMap API, respectively.) Next, we need to tell our function what to do when that call is successful and unsuccessful by listening to the readyState (4 means everything is going swimmingly.) For our extension, we’re just going to grab the name of our city and throw it into our main-text HTML element along with a short and kinda silly message. We’re also going to be using the main weather descriptor that we get back to search for an appropriate background picture in that updateBackground function. Let’s write that now.

const updateBackground = (weather) => {
xhr.open("GET", `https://api.unsplash.com/photos/random?query=${weather}&client_id=<myUnsplashKey>`, true)
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
let response = JSON.parse(xhr.response)
response.urls.full ? localWeather.style.background = `url(${response.urls.regular}) no-repeat center center fixed` : null
else {

So here we’re doing basically the same thing as when we got the weather, with the exception being that if something goes wrong with this API call we call the “setDefaultBackground” function that uses a predetermined list of weather pictures to use for the background of the extension instead of randomly searching with our local weather word. That function looks like this:

const clearUrl = 'https://unsplash.com/search/clear-sky?photo=fuAy6Gs8QCw';
const cloudUrl = 'https://unsplash.com/search/cloudy?photo=S7ChB4FBboI';
const rainUrl = 'https://unsplash.com/search/rain?photo=vg6zo_GJf1k';
const snowUrl = 'https://unsplash.com/search/snow?photo=67t2GJcD5PI';
const setDefaultBackground = (weather) => {
weather.toLowerCase().includes('rain') ? localWeather.style.background = `url(${rainUrl}) no-repeat center center fixed` : null;
weather.toLowerCase().includes('clear') ? localWeather.style.background = `url(${clearUrl}) no-repeat center center fixed` : null;
weather.toLowerCase().includes('snow') ? localWeather.style.background = `url(${snowUrl}) no-repeat center center fixed` : null;
weather.toLowerCase().includes('cloud') ? localWeather.style.background = `url(${cloudUrl}) no-repeat center center fixed` : null;

Yes, this could be way prettier, but this is just in case I run out of API calls (Unsplash only gives you 50 per hour) or internet. (I should definitely refactor this, though.)

So…that’s our code. The last step is to throw it into Chrome and see if it works. It’s SUPER easy to do that by following these steps.

  1. Type chrome://extensions/ into your browser (Chrome. Your browser has to be Chrome.)
  2. Make sure the “Developer Mode” checkbox in the top-right corner is checked.
  3. Click “Load unpacked extension”, then navigate to wherever your directory is at, and you should see a new icon in your extensions bar that has the first letter of your extension’s name. Click it, and you should see something like this.
If you’re in Denver. And it’s clear. And Unsplash returns this particular image.

Note: You need to go to this extensions page and reload it to see any changes you’ve made in your code take effect.

I haven’t deployed this yet so I can’t speak to what that process is like, but I’m pretty sure we just made a real Chrome extension. We could (and should, probably) add things like temperature and tweak the message and make the styling prettier, but overall not bad for a first attempt, I think.

P.S. — I also made a desktop version of this app with Electron, which you can check out here.