Firebase Push Notifications In React

Firebase Push Notifications In React

Firebase Push Notifications In React

Chidi Orji

2020-06-29T13:30:00+00:00 2020-07-05T20:04:23+00:00

Notifications have become a stable part of the web nowadays. It’s not uncommon to come across sites asking for permission to send notifications to your browser. Most modern web browsers implement the push API and are able to handle push notifications. A quick check on caniuse shows that the API enjoys wide support among modern chrome-based browsers and Firefox browser.

There are various services for implementing notifications on the web. Notable ones are Pusher and Firebase. In this article, we’ll implement push notifications with the Firebase Cloud Messaging (FCM) service, which is “a cross-platform messaging solution that lets you reliably send messages at no cost”.

I assume that the reader has some familiarity with writing a back-end application in Express.js and/or some familiarity with React. If you’re comfortable with either of these technologies, then, you could work with either the frontend or backend. We will implement the backend first, then move on to the frontend. In that way, you can use whichever section appeals more to you.

So let’s get started.

Types Of Firebase Messages

The Firebase documentation specifies that an FCM implementation requires two components.

  1. A trusted environment such as Cloud Functions for Firebase or an app server on which to build, target, and send messages.
  2. An iOS, Android, or web (JavaScript) client app that receives messages via the corresponding platform-specific transport service.

We will take care of item 1 in our express back-end app, and item 2 in our react front-end app.

The docs also state that FCM lets us send two types of messages.

  1. Notification messages (sometimes thought of as “display messages”) are handled by the FCM SDK automatically.
  2. Data messages are handled by the client app.

Notification messages are automatically handled by the browser on the web. They can also take an optional data payload, which must be handled by the client app. In this tutorial, we’ll be sending and receiving data messages, which must be handled by the client app. This affords us more freedom in deciding how to handle the received message.

Setting Up A Firebase Project

The very first thing we need to do is to set up a Firebase project. FCM is a service and as such, we’ll be needing some API keys. This step requires that you have a Google account. Create one if you don’t already have one. You can click here to get started.

After setting up your Google account, head on to the Firebase console.

Click on add project. Enter a name for your project and click on continue. On the next screen, you may choose to turn off analytics. You can always turn it on later from the Analytics menu of your project page. Click continue and wait a few minutes for the project to be created. It’s usually under a minute. Then click on continue to open your project page.

Once we’ve successfully set up a project, the next step is to get the necessary keys to work with our project. When working with Firebase, we need to complete a configuration step for the frontend and backend separately. Let’s see how we can obtain the credentials needed to work with both.


On the project page, click on the icon to add Firebase to your web app.

Add Firebase to a web project
Add Firebase to a web project. (Large preview)

Give your app a nickname. No need to set up Firebase hosting. Click on Register app and give it a few seconds to complete the setup. On the next screen, copy out the app credentials and store them somewhere. You could just leave this window open and come back to it later.

Firebase web app credentials
Firebase web app credentials. (Large preview)

We’ll be needing the configuration object later. Click on continue to console to return to your console.


We need a service account credential to connect with our Firebase project from the backend. On your project page, click on the gear icon next to Project Overview to create a service account for use with our Express backend. Refer to the below screenshot. Follow steps 1 to 4 to download a JSON file with your account credentials. Be sure to keep your service account file in a safe place.

Steps for creating a service account credential
Steps for creating a service account credential. (Large preview)

I’ll advise you not to download it until you’re ready to use it. Just remember to come back to these sections if you need a refresher.

So now we’ve successfully set up a Firebase project and added a web app to it. We’ve also seen how to get the credentials we need to work with both the frontend and backend. Let’s now work on sending push notifications from our express backend.

Getting Started

To make it easier to work through this tutorial, I’ve set up a project on Github with both a server and a client. Usually, you’ll have a separate repo for your backend and frontend respectively. But I’ve put them together here to make it easier to work through this tutorial.

Create a fork of the repo, clone it to your computer, and let’s get our front-end and back-end servers started.

  1. Fork the repo and check out the 01-get-started branch.
  2. Open the project in your code editor of choice and observe the contents.
  3. In the project root, we have two folders, client/ and server/. There’s also a .editorconfig file, a .gitignore, and a
  4. The client folder contains a React app. This is where we will listen for notifications.
  5. The server folder contains an express app. This is where we’ll send notifications from. The app is from the project we built in my other article How To Set Up An Express API Backend Project With PostgreSQL.
  6. Open a terminal and navigate to the client/ folder. Run the yarn install command to install the project dependencies. Then run yarn start to start the project. Visit http://localhost:3000 to see the live app.
  7. Create a .env file inside the server/ folder and add the CONNECTION_STRING environment variable. This variable is a database connection URL pointing to a PostgreSQL database. If you need help with this, check out the Connecting The PostgreSQL Database And Writing A Model section of my linked article. You should also provide the PORT environment variable since React already runs on port 3000. I set PORT=3001 in my .env file.
  8. Open a separate terminal and navigate to the server/ folder. Run the yarn install command to install the project dependencies. Run yarn runQuery to create the project database. Run yarn startdev to start the project. Visit http://localhost:3001/v1/messages and you should see some messages in a JSON format.
Frontend and backend servers running
Frontend and backend servers running. (Large preview)
React frontend app running
React frontend app running. (Large preview)
Express backend app running
Express backend app running. (Large preview)

Now that we have our front-end and back-end apps running, let’s implement notifications in the backend.

Setting Up Firebase Admin Messaging On The Backend

Sending out push notifications with FCM on the backend requires either the Firebase admin SDK or the FCM server protocols. We’ll be making use of the admin SDK in this tutorial. There’s also the notifications composer, which is good for “testing and sending marketing and engagement messages with powerful built-in targeting and analytics”.

In your terminal, navigate to the server/ folder and install the Admin SDK.

# install firebase admin SDK
yarn add firebase-admin

Open your .env file and add the following environment variable.


The value of this variable is the path to your downloaded service account credentials. At this point, you probably want to go back to the section where we created the service account for our project. You should copy the admin initialization code from there and also download your service account key file. Place this file in your server/ folder and add it to your .gitignore.

Remember, in an actual project, you should store this file in a very secure location on your server. Don’t let it get into the wrong hands.

Open server/src/settings.js and export the application credentials file path.

# export the service account key file path
export const googleApplicationCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;

Create a file server/src/firebaseInit.js and add the below code.

import admin from 'firebase-admin'; import { googleApplicationCredentials } from './settings' const serviceAccount = require(googleApplicationCredentials); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: 'your-database-url-here'
}); export const messaging = admin.messaging();

We import the admin module from firebase-admin. We then initialize the admin app with our service account file. Finally, we create and export the messaging feature.

Note that I could have passed the path to my service account key file directly, but it is the less secure option. Always use environment variables when dealing with sensitive information.

To check that you completed the initialization successfully, open up server/src/app.js and include the following lines.

import { messaging } from './firebaseInit'

We import the messaging instance and log it in the console. You should see something like the picture below. You should remove these once you verify that your admin is set up correctly.

Console log of messaging feature
Console log of messaging feature. (Large preview)

If you run into any problems, you can check out the 02-connect-firebase-admin branch of my repo for comparison.

Now that we’ve successfully setup admin messaging, let’s now write the code to send the notifications.

Sending Push Notifications From The Backend

FCM data message configuration is very simple. All you have to do is supply one or more target(s) and a JSON of the message you wish to send to the client(s). There are no required keys in the JSON. You alone decide what key-value pairs you want to include in the data. The data messages form works across all platforms, so our notification could also be processed by mobile devices.

There are additional configurations for other platforms. For example, there’s an android settings that only work with android devices and apns settings that work on only iOS devices. You can find the configuration guide here.

Create a file server/src/notify.js and enter the below code.

import { messaging } from './firebaseInit'; export const sendNotificationToClient = (tokens, data) => { // Send a message to the devices corresponding to the provided // registration tokens. messaging .sendMulticast({ tokens, data }) .then(response => { // Response is an object of the form { responses: [] } const successes = response.responses.filter(r => r.success === true) .length; const failures = response.responses.filter(r => r.success === false) .length; console.log( 'Notifications sent:', `${successes} successful, ${failures} failed` ); }) .catch(error => { console.log('Error sending message:', error); });

We created a function that accepts an array of token strings and a data object. Each token string represents a device that has accepted to receive notifications from our back-end application. The notification will be sent to each client in the tokens array. We’ll see how to generate the token in the front-end section of the tutorial.

The messaging instance’s sendMulticast method returns a promise. On success, we get an array from which we count the number of successes as well as failed notifications. You could certainly handle this response anyhow you want.

Let’s use this function to send out a notification each time a new message is added to the database.

Open server/src/controllers/message.js and update the addMessage function.

import { sendNotificationToClient } from '../notify'; export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); const tokens = []; const notificationData = { title: 'New message', body: message, }; sendNotificationToClient(tokens, notificationData); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); }

This function handles a post request to the /messages endpoint. Once a message is successfully created, a notification is sent out by the sendNotificationToClient function followed by the response to the client. The only missing piece in this code is the tokens to send the notifications to.

When we connect the client app, we’ll copy the generated token and paste it in this file. In a production app, you’ll store the tokens somewhere in your database.

With this last piece of code, we’ve completed the back-end implementation. Let’s now switch over to the frontend.

The corresponding branch in my repo at this point is 03-send-notification.

Setting Up Firebase Messaging Notifications On The Client

Let’s take a look at the main components of our front-end React app.

Open up client/src/App.js and inspect the content. I’ll leave out most of the import statements and just look at the program logic.

# library imports import { Messaging } from './Messaging'; axios.defaults.baseURL = 'http://localhost:3001/v1'; const App = () => { return ( <Fragment> <ToastContainer autoClose={2000} position="top-center" /> <Navbar bg="primary" variant="dark"> <Navbar.Brand href="#home">Firebase notifictations with React and Express</Navbar.Brand> </Navbar> <Container className="center-column"> <Row> <Col> <Messaging /> </Col> </Row> </Container> </Fragment> );
export default App;

This is a regular react component styled with react-bootstrap. There’s a toast component right at the top of our app, which we shall use to display notifications. Note that we also set the baseURL for the axios library. Everything of note happens inside the <Messaging /> component. Let’s now take a look at its content.

Open up client/src/Messaging.js and inspect the content.

export const Messaging = () => { const [messages, setMessages] = React.useState([]); const [requesting, setRequesting] = React.useState(false); React.useEffect(() => { setRequesting(true); axios.get("/messages").then((resp) => { setMessages(; setRequesting(false); }); }, []); return ( <Container> {/* form goes here */} <div className="message-list"> <h3>Messages</h3> {requesting ? ( <Spinner animation="border" role="status"> <span className="sr-only">Loading...</span> </Spinner> ) : ( <> {, index) => { const { name, message } = m; return ( <div key={index}> {name}: {message} </div> ); })} </> )} </div> </Container> );

We have two state variables, messages and requesting. messages represent the list of messages from our database and requesting is for toggling our loader state. We have a React.useEffect block where we make our API call to the /messages endpoint and set the returned data in our messages state.

In the return statement, we map over the messages and display the name and message fields. On the same page, we include a form for creating new messages.

<Formik initialValues={{ name: "", message: "", }} onSubmit={(values, actions) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); actions.setSubmitting(false); toast.success("Submitted succesfully"); }, 1000); }}
> {(prop) => { const { handleSubmit, handleChange, isSubmitting } = prop; return ( <> <InputGroup className="mb-3"> <InputGroup.Prepend> <InputGroup.Text id="basic-addon1">Name</InputGroup.Text> </InputGroup.Prepend> <FormControl placeholder="Enter your name" onChange={handleChange("name")} /> </InputGroup> <InputGroup className="mb-3"> <InputGroup.Prepend> <InputGroup.Text id="basic-addon1">Message</InputGroup.Text> </InputGroup.Prepend> <FormControl onChange={handleChange("message")} placeholder="Enter a message" /> </InputGroup> {isSubmitting ? ( <Button variant="primary" disabled> <Spinner as="span" size="sm" role="status" animation="grow" aria-hidden="true" /> Loading... </Button> ) : ( <Button variant="primary" onClick={() => handleSubmit()}> Submit </Button> )} </> ); }}

We’re using the Formik library to manage our form. We pass the <Formik /> component an initialvalues props, an onSubmit prop and the form component we want to render. In return, we get back some handy functions such as handleChange which we can use to manipulate our form inputs, and handleSubmit which we use to submit the form. isSubmitting is a boolean that we use to toggle the submit button state.

I encourage you to give formik a try. It really simplifies working with forms. We will replace the code in the onSubmit method later.

Let’s now implement the method that will request a browser’s permission and assign it a token.

To start using Firebase in the frontend, we have to install the Firebase JavaScript client library. Note that this is a different package from the firebase-admin SDK.

# install firebase client library
yarn add firebase

Create a file client/src/firebaseInit.js and add the following content.

import firebase from 'firebase/app';
import 'firebase/messaging'; const config = { apiKey: "API-KEY", authDomain: "AUTH-DOMAIN", databaseURL: "DATABASE-URL", projectId: "PROJECT-ID", storageBucket: "STORAGE-BUCKET", messagingSenderId: "MESSAGING-SENDER-ID", appId: "APP-ID"
}; firebase.initializeApp(config);
const messaging = firebase.messaging(); // next block of code goes here

The Firebase docs state that:

“The full Firebase JavaScript client includes support for Firebase Authentication, the Firebase Realtime Database, Firebase Storage, and Firebase Cloud Messaging.”

So here, we import only the messaging feature. At this point, you could refer to the section on creating a Firebase project to get the config object. We then initialize Firebase and export the messaging feature. Let’s add in the final block of code.

export const requestFirebaseNotificationPermission = () => new Promise((resolve, reject) => { messaging .requestPermission() .then(() => messaging.getToken()) .then((firebaseToken) => { resolve(firebaseToken); }) .catch((err) => { reject(err); }); }); export const onMessageListener = () => new Promise((resolve) => { messaging.onMessage((payload) => { resolve(payload); }); });

The requestFirebaseNotificationPermission function requests the browser’s permission to send notifications and resolves with a token if the request is granted. This is the token that FCM uses to send a notification to the browser. It is what triggers the prompt you see on browsers asking for permission to send a notification.

The onMessageListener function is only invoked when the browser is in the foreground. Later, we will write a separate function to handle the notification when the browser is in the background.

Open up client/src/App.js and import the requestFirebaseNotificationPermission function.

import { requestFirebaseNotificationPermission } from './firebaseInit'

Then inside the App function, add the below code before the return statement.

requestFirebaseNotificationPermission() .then((firebaseToken) => { // eslint-disable-next-line no-console console.log(firebaseToken); }) .catch((err) => { return err; });

Once the app loads this function runs and requests the browser’s permission to show notifications. If the permission is granted, we log the token. In a production app, you should save the token somewhere that your backend can access. But for this tutorial, we’re just going to copy and paste the token into the back-end app.

Now run your app and you should see the notification request message. Click allow and wait for the token to be logged to the console. Since you’ve granted the browser permission, if we refresh the page you won’t see the banner anymore, but the token will still be logged to the console.

App request to show notifications
App request to show notifications. (Large preview)

You should know that Firefox browser (v75) doesn’t ask for notification permission by default. The permission request has to be triggered by a user-generated action like a click.

This is a good point for me to commit my changes. The corresponding branch is 04-request-permission.

Let’s now complete the code for saving a message to our database.

Open up client/src/Messaging.js and replace the onSubmit function of our form with the below code.

onSubmit={(values, actions) => { axios .post("/messages", values) .then((resp) => { setMessages(; actions.setSubmitting(false); toast.success("Submitted succesfully"); }) .catch((err) => { console.log(err); toast.error("There was an error saving the message"); });

We make a post request to the /messages endpoint to create a new message. If the request succeeds we take the returned data and put it at the top of the messages list. We also display a success toast.

Let’s try it out to see if it works. Start the front-end and back-end servers. Before trying out the post request, open server/src/controllers/messages.js and comment out the line where we’re sending the notification.

# this line will throw an error if tokens is an empty array comment it out temporarily
// sendNotificationToClient(tokens, notificationData);

Try adding some messages to the database. Works? That’s great. Now uncomment that line before continuing.

Copy the notification token from the developer console and paste it into the tokens array. The token is a very long string, as shown below.

 const tokens = [ 'eEa1Yr4Hknqzjxu3P1G3Ox:APA91bF_DF5aSneGdvxXeyL6BIQy8wd1f600oKE100lzqYq2zROn50wuRe9nB-wWryyJeBmiPVutYogKDV2m36PoEbKK9MOpJPyI-UXqMdYiWLEae8MiuXB4mVz9bXD0IwP7bappnLqg', ];

Open client/src/Messaging.js, import the onMessageListener and invoke it just under the useEffect block. Any position within the function is fine as long it’s before the return statement.

import { onMessageListener } from './firebaseInit'; React.useEffect(() => { ... }, []); onMessageListener() .then((payload) => { const { title, body } =;`${title}; ${body}`); }) .catch((err) => { toast.error(JSON.stringify(err)); });

The listener returns a promise which resolves to the notification payload on success. We then display the title and body in a toast. Note that we could have taken any other action once we receive this notification but I’m keeping things simple here. With both servers running, try it out and see if it’s working.

Works? That’s great.

In case you run into problems, you could always compare with my repo. The corresponding branch at this point is 05-listen-to-notification.

There’s just one bit we need to take care of. Right now we can only see notifications when the browser is in the foreground. The point about notifications is that it should pop up whether the browser is in the foreground or not.

If we were to be sending a display message i.e. we included a notification object in our notification payload, the browser will take care of that on its own. But since we’re sending a data message, we have to tell the browser how to behave in response to a notification when our browser is in the background.

To handle the background notification we need to register a service worker with our front-end client.

Create a file client/public/firebase-messaging-sw.js and enter the following content:

importScripts(''); const config = { apiKey: "API-KEY", authDomain: "AUTH-DOMAIN", databaseURL: "DATABASE-URL", projectId: "PROJECT-ID", storageBucket: "STORAGE-BUCKET", messagingSenderId: "MESSAGING-SENDER-ID", appId: "APP-ID"
}; firebase.initializeApp(config);
const messaging = firebase.messaging(); messaging.setBackgroundMessageHandler(function(payload) { console.log('[firebase-messaging-sw.js] Received background message ', payload); const notificationTitle =; const notificationOptions = { body:, icon: '/firebase-logo.png' }; return self.registration.showNotification(notificationTitle, notificationOptions);
}); self.addEventListener('notificationclick', event => { console.log(event) return event;

At the top of the file, we’re importing the firebase-app and the firebase-messaging libraries since we only need the messaging feature. Don’t worry if the import syntax is new. It’s a syntax for importing external scripts into service worker files. Make sure that the version being imported is the same as the one in your package.json. I’ve run into issues that I solved by harmonizing the versions.

As usual, we initialize Firebase, then we invoke the setBackgroundMessageHandler, passing it a callback, which receives the notification message payload. The remaining part of the code specifies how the browser should display the notification. Notice that we can also include an icon to display as well.

We can also control what happens when we click on the notification with the notificationclick event handler.

Create a file client/src/serviceWorker.js and enter the below content.

export const registerServiceWorker = () => { if ('serviceWorker' in navigator) { navigator.serviceWorker .register('firebase-messaging-sw.js') .then(function (registration) { // eslint-disable-next-line no-console console.log('[SW]: SCOPE: ', registration.scope); return registration.scope; }) .catch(function (err) { return err; }); }

This function registers our service worker files. Note that we have replaced the more detailed version generated by React. We first check if the serviceWorker is present in the navigator object. This is simple browser support. If the browser supports service workers, we register the service worker file we created earlier.

Now open client/src/index.js, import this function, and invoke it.

# other imports import { registerServiceWorker } from './serviceWorker' ReactDOM.render( ...
); registerServiceWorker()

If all goes well, you should see the service worker’s scope logged to your console.

Open http://localhost:3000/messaging in a second browser and create a message. You should see the notification from the other browser come into view.

Background and foreground notifications
Background and foreground notifications. (Large preview)

With that, we’ve come to the end of this tutorial. The corresponding branch in my repo is 06-handle-background-notification.


In this article, we learned about the different types of notification messages we can send with the Firebase Cloud Messaging (FCM). API. We then implemented the “data message” type on the backend. Finally, we generated a token on the client app which we used to receive notification messages triggered by the back-end app. Finally, we learned how to listen for and display the notification messages when the browser is in either the background or foreground.

I encourage you to take a look at the FCM docs to learn more.

Smashing Editorial (ks, ra, yk, il)

Advanced Strategies for Selling on Instagram

Last month I addressed content ideas to maximize an Instagram feed to attract followers. In this post, I’ll discuss strategies to optimize Instagram posts to retain and engage an audience of potential buyers.

Selling on Instagram

Understand the algorithm. The goal of Instagram’s algorithm is to foster a positive user experience by showing relevant content. What users see is tied to the accounts they engage with, direct message to, and search for or tag.

It’s crucial for a brand — including ecommerce — to show up on Instagram consistently. Whether that’s via posts or Stories, new, consistent content keeps your image in follower’s feeds and enhances your standing in the hard-to-master algorithm.

There is no rule for the volume of content on Instagram. Larger companies may post daily or even multiple times per day. But that could be unrealistic for a small business with a small following.

Consistency is key. Neil Patel, the marketing guru, told Forbes, “If you make a habit of posting several times a day and then transition to only a few times a week, you will start to lose followers and generate less engagement per post.”

Scheduling posts. Once you’ve determined the frequency, optimize your posts by time of day. Later, a popular scheduling app, analyzed 12 million Instagram posts and found that the best time to post is between 9 a.m. and 11 a.m. Eastern Time.

But that could differ for your account. To find the best time, use (i) a scheduling app like Later for audience metrics, or (ii) Instagram’s Audience Insights (for business accounts) to see when most of your audience is online.

For example, according to Instagram Insights, the best time to post to my business account is Thursdays at 9 p.m. Eastern Time.

According to Instagram Insights, the best time to post to the author’s business account is Thursdays at 9 p.m. Eastern Time.

According to Instagram Insights, the best time to post to the author’s business account is Thursdays at 9 p.m. Eastern Time.

Optimize your company’s name. Names are a searchable field on Instagram, which makes it an optimal place to add keywords to your name and bio.

When I search for “tools” on Instagram, as an example, the results are tool-related accounts, from hair styling tools (“jacksbeautyline”) to hardware tools (“toolstoday”) to business tools (“toolstogrow”).

Searching for “tools” on Instagram produces tool-related results — hair styling tools ("jacksbeautyline") to hardware tools ("toolstoday") to business tools ("toolstogrow").

Searching for “tools” on Instagram produces tool-related results — hair styling tools (“jacksbeautyline”) to hardware tools (“toolstoday”) to business tools (“toolstogrow”).

“Tools” is not in the handle for the “thebeachwaver.” The profile, however, does include it: “Innovative Haircare + Tools.” Thus @thebeachwaverwhen can appear in “tools” results, too.

The handle @thebeachwaver does not include “tool.” But the profile does include it: "Innovative Haircare + Tools."

The handle @thebeachwaver does not include “tools.” But the profile does include it: “Innovative Haircare + Tools.”

Engagement. It’s not enough to optimize posts and send them into the abyss. You want followers to engage. Here’s how:

  • Reply to all their comments promptly.
  • Answer their questions publicly.
  • Share their relevant content to your feed or Stories.

Hashtags. Think of hashtags as a search-optimization technique. Each post can include up to 30 hashtags, which is plenty for the most ecommerce businesses. Consider three types of hashtags:

  • Geotags include a city or locale, such as “swimsuitsmiami.” Geotags are also helpful for brick-and-mortar stores to help consumers find directions, hours, and so on.
  • Branded tags are specific to your product, name, or slogan, such as “beyourincredibleself.” All of your posts will appear when an Instagram user clicks that hashtag.
  • Niche tags are hyper-focused with fewer searches, such as “deepseafishing” versus the broader “fishing.”

Try to place bulky tags, such as geotags and niche tags, in the first comment of your post, as in the example below from @dermavidualsny. Moreover, inserting a relevant hashtag (#corneotherapy) in the caption is a clever way to expand the reach.

Place bulky tags in the first comment of a post. Insert a relevant hashtag, such as #corneotherapy, in the caption to expand the reach.

Place bulky tags in the first comment of a post. Insert a relevant hashtag, such as #corneotherapy, in the caption to expand the reach.

Find your voice. A ton of companies on Instagram compete for prospective customers. Stand out by having a unique tone and voice that your followers can count on. It creates loyalty and invites engagement. Instagram users are mostly younger and more informal than, say, users on Facebook, Twitter, and LinkedIn. A casual vibe is best.

Shopify’s App Store Is Good and Bad, Says ‘Unofficial’ Expert

Kurt Elster’s “The Unofficial Shopify Podcast” has been downloaded more than 1 million times. His agency, Ethercycle, helps Shopify merchants drive revenue and profit. And his newsletter at contains tips and strategies to scale a Shopify store.

“I eat, sleep, and breathe Shopify. I even have a Shopify license plate,” Elster told me.

You might call Elster the unauthorized Mr. Shopify. I recently spoke with him about the platform, notable merchants, and his pet peeves, among other topics. What follows is the entire audio version of that conversation and a transcript, which is edited for length and clarity.

Eric Bandholz: Tell us about yourself.

Kurt Elster: I’ve been a Shopify expert since 2011. I have worked on Shopify exclusively since 2015. I eat, sleep, and breathe Shopify. I even have a Shopify license plate.

Bandholz: Is Shopify the place to be? Is it still hot?

Elster: Yes. Over a million merchants are on Shopify. Some of the real power of it is the partner ecosystem. They’re up to 12,000 partners. Having access to an army of people who can help you grow your store, whether it’s through apps, integrations, or professional services, adds a ton of value to the core platform.

Bandholz: What are you seeing from people who reach out to you for consulting? Are they larger companies on the Shopify Plus side? Or startups?

Elster: It is all over the board. Right now there is a gold rush into ecommerce. I am seeing more new store builds than ever before. Our clientele tends to be Shopify merchants who have bootstrapped and validated their business. They approach us to take it to the next level.

We do a lot of front-end work, setting up the theme. That’s the hardest part for merchants. Many merchants can’t see the forest through the trees on their own website. They spend more time on it than anybody else, which makes it tough to see minor issues. So having a third party do it could be beneficial.

Bandholz: What are some of the biggest mistakes people make on Shopify?

Elster: The best part about Shopify is the app ecosystem — the app store. And the worst part about Shopify is the app store. No matter what feature or integration you need, there is probably one or more apps for it. And, likely, they’re all reasonably priced. Some will even be free. But it is easy to get addicted to apps. You start playing app roulette. You think, “If I just get the right one, it’s going to seriously boost my sales.”

Well, no, it doesn’t work that way. But the larger problem is as you install these apps, each one has a payload. Each one adds weight to the site, and they also add to your monthly recurring expenses. So if you’ve got one app, fine. If you’ve got eight apps, fine. But 40 apps — that’s a problem.

I have been retained more times than I can count just to figure out installed apps — which ones to keep and which to remove. So that’s a trap that people can fall into. It’s similar to WordPress plugins.

My current pet peeve is the main menu. It’s the front door to an ecommerce site. It’s how visitors find products. But if I load up a random independent online store, the main menu is “Home,” “About,” “Blog,” “Contact,” and then there’s a single word that says “Shop.” And it’s a drop-down menu. That’s the most important part! It’s entirely hidden behind a drop-down.

Go to major ecommerce sites. None of them do that. The main menu is devoted to shopping. All of that other stuff is in a secondary menu or the footer.

Bandholz: Tell us about some innovative brands.

Elster: My favorite client at the moment is Hoonigan, an automotive lifestyle brand. They sell lifestyle apparel, mainly. At one point years ago they had the most viral video on YouTube for a car video called Gymkhana that’s like half driving. Think if you combined driving and, I don’t know, a Cirque de Soleil show. It’s quite the activity.

It’s stunt driving, basically. It’s very cool. So it went viral, and an automotive lifestyle brand was born out of that. It’s a very creative team, about 30 people in Long Beach, California. They do a phenomenal job in their marketing, their designs, and their ads. They have built a community. That’s the magic that a lot of brands miss — building a genuine sense of community.

Bandholz: We’ve talked about Shopify themes. Do you have recommendations for themes? There are a ton of them.

Elster: I have opinions on Shopify themes. So does the theme team at Shopify. I have talked to members of that team at the various Shopify Unite conferences. The team is extremely selective as to what’s in the theme store. Themes that are ultimately approved typically have limited options and settings to make them easier to use and less intimidating.

My favorite in the last couple of years is the Turbo theme by Out of the Sandbox. They’ve been consistently updating it. I love it. I used it on [Jay] Leno’s Garage. I’ve used it on CORSA Performance. Just an endless list of sites.

But even though Out of the Sandbox has several themes in the store (including popular ones such as Parallax and Retina), Turbo is not in there. That’s because it has a ton of options jammed into it. I don’t think it would ever be approved because it’s intimidating to a new merchant.

There’s a balance there. If you’re launching your first Shopify store, I wouldn’t start with a big, expensive theme. I would start with a simple free theme and then switch to a bigger, more involved one later.

And the last thing I would do is buy a theme from whatever marketplace. Anytime we’ve used themes outside of the Shopify store, we have regretted it. Again, the team is extremely stringent about what gets in there.

Bandholz: So how do you differentiate websites when the bones are the same?

Elster: That a common fear. It’s unfounded, however.

For example, I live in a subdivision with, perhaps, 200 houses. Three are identical to mine. All three look completely different because they have different landscaping, different lots, they face different directions, and they have different decor and paint. They don’t look like the same house.

The same is true of themes and even more so. A theme is just a way to showcase your content and brand. But don’t use the default styling, colors, and fonts and just insert your logo. At the very least, change the colors and typography to match the brand. It sounds simple, but a lot of people skip it. Change the design of the header. That will immediately alter the entire feel.

Bandholz: A lot of companies with huge catalogs have shied away from Shopify. Is that a valid fear?

Elster: No. We’re working on a large site right now. I wish it were public so I could tell you the name. It had 80,000 products when we started working on it. It now has 120,000. By the end of the year, we’ll have 200,000.

Certainly we had to manage some things. But the site is now one of the fastest I’ve ever worked on. When we first started, it took 30 to 40 seconds to load a collection page because of the number of products we were filtering through. But Shopify is giving us new technologies such as React. A catalog with six figures worth of product can load in two to three seconds.

Bandholz: I could talk all day about Shopify. Unfortunately, we don’t have all day. How can our listeners find you?

Elster: My Twitter handle is @Kurtinc. Other than Twitter, the best way to reach me is via my newsletter. Head to, sign up for my newsletter. It comes from my actual email address. You can hit reply, and it will go to my inbox.

Bandholz: Everyone should listen to your podcast, “The Unofficial Shopify Podcast.” It’s amazing.

Elster: I’ve got opinions.