Posting Instagram Reels using Node.js and the art of overcoming limitations

After all, why spend 30 seconds doing something manually when you can spend 30 hours automating it?

Written in October 14, 2023 - 🕒 7 min. read

Hey guys! It’s hard to be a geek these days, so many events are exclusive to media people and influencers - even E3 was a trade-only show for many years. So let’s say you’re already happy with your job, and you don’t really want to become a journalist, so your best bet is to become an influencer, and then maybe get invited to the next [insert name of gaming franchise] release event, right?

I’m pretty lucky to have worked as a freelancer journalist for various media outlets in Brazil, but since I moved to the Netherlands, I can’t go to these events anymore, since I have no media contacts here, and also don’t speak Dutch. One of the things I’ve already missed this year was the The Last of Us release event in Amsterdam and recently (just last week) the Marvel Spider-Man 2 event in Hilversum.

Ok so now what? Well, I’ve decided to become an influencer, and I’ve started posting on Instagram, but I don’t really enjoy posting stuff on Instagram, I’d rather spend my time coding, and of course, becoming an Instagram celebrity these days is not an easy task, so I decided to automate some parts of it, after all, Meta provides an API that allows us to do many things, including posting Stories, Reels, or photos, so what could go wrong?

Instagram automation
Instagram automation

The Instagram API

Awesome, so we simply npm install axios and start posting on Instagram, right? Well… for some reason, you can’t upload videos to Instagram directly via the API, you need to provide an URL which then they will download your video from… like… what? I mean, we’ve sent people to Mars, but we can’t send a video to Instagram without a link?

But fear not, I am here to help you solve this problem, my fellow developer. I’ve created a Node.js script that will allow you to post videos to Instagram, and I’ll explain how it works.

The Instagram video posting dilemma

Before we dive deep, let’s get our tools ready. Install the necessary libraries:

npm install axios @ngrok/ngrok express fluent-ffmpeg ffmpeg-static

Now, let’s break down the code and understand the magic behind it.

First, we need to set up our environment. We’re using axios for HTTP requests, ngrok to expose our local server to the world (because, you know, Instagram needs a link), and express to serve our video.

const axios = require('axios');
const ngrok = require('@ngrok/ngrok');
const express = require('express');

const GRAPH_API_VERSION = 'v18.0';
const PORT = 3000;
const expressApp = express();

Now, let’s get to the fun part. We need to upload our video to a container on Instagram. But remember, we’re not actually uploading the video. We’re just telling Instagram where to find it. So we need to create a container, and then upload the video to it.

const uploadReelsToContainer = async (
) => {
  const response = await
      access_token: accessToken,
      media_type: 'REELS',
      video_url: videoUrl,
      cover_url: coverUrl,


This code simply makes a POST request to the Instagram API to create a container and “upload” the video to it.

The “upload” is in quotes because we’re not actually uploading the video, we’re just telling Instagram where to find it.

Cool, but you must be thinking, “where do I get the accessToken and instagramAccountId from?” Well, you need to create a Facebook app, and then create an Instagram account, and then link your Instagram account to your Facebook app. You can find more information about this here. To be honest, this is the hardest part of the process, but once you get it working, you’re good to go.

Ah, wait, you also want to know how to get the videoUrl and coverUrl, right? Patience, my sweet padawan, we’ll get there.

After the container is created, we need to check its status. We need to wait until the video is fully downloaded by Instagram.

const getStatusOfUpload = async (accessToken, igContainerId) => {
  const response = await axios.get(
    { params: { access_token: accessToken, fields: 'status_code' } }


If the status is FINISHED, then we can proceed to the next step, which is finally publishing the video, or as Meta calls it: media publishing.

const publishMediaContainer = async (accessToken, instagramAccountId, creationId) => {
  const response = await
    { access_token: accessToken, creation_id: creationId }


And what is the point of posting it and getting all these weird IDs? What we actually want is the Instagram post URL, right? For that, we need to get the permalink_url from the response.

const fetchPermalink = async (accessToken, mediaId) => {
  const response = await axios.get(
    { params: { access_token: accessToken, fields: 'permalink' } }


Let’s stitch it all together

I’ve been postponing this part for a while, but now it’s time to explain how to get the videoUrl and coverUrl, and this is where ngrok comes into play. We need to create a local express server that will serve our video and cover, and then expose it to the world using ngrok.


expressApp.get('/video', (req, res) => res.sendFile(videoFilePath));
expressApp.get('/cover', (req, res) => res.sendFile(thumbnailFilePath));

With this code, we will serve our video and cover on http://localhost:3000/video and http://localhost:3000/cover, respectively. The variables videoFilePath and thumbnailFilePath should contain a local path to the video and cover files.

Now we’ll expose it to the world using ngrok. This will give us a URL that we can use to upload our video to Instagram.

const listener = await ngrok.connect(port);
const url = listener.url();

const videoUrl = `${url}/video`;
const coverUrl = `${url}/cover`;

And now putting it all together:

const postInstagramReel = async (
) => {
  const expressApp = express();

  expressApp.get('/video', (req, res) => res.sendFile(videoFilePath));
  expressApp.get('/cover', (req, res) => res.sendFile(thumbnailFilePath));

  const listener = await ngrok.connect(PORT);
  const url = listener.url();

  const { id: containerId } = await uploadReelsToContainer(

  let status = null;
  const TWO_MINUTES = 2 * 60 * 1000;
  const startTime =;

  while (status !== 'FINISHED') {
    if ( - startTime > TWO_MINUTES) {
      await ngrok.disconnect();
      throw new Error('Upload took longer than 2 minutes.');

    status = await getStatusOfUpload(accessToken, containerId);
    await new Promise((r) => setTimeout(r, 1000));

  const { id: creationId } = await publishMediaContainer(accessToken, pageId, containerId);

  const { permalink } = await fetchPermalink(accessToken, creationId);

  await ngrok.disconnect();

  return { creationId, permalink };

This function will start the express server and set the ngrok URL. Then it will “upload” the video to Instagram, and wait until the video is fully downloaded. After that, it will publish the video and return the Instagram post URL.

And that’s it! Now you can post videos to Instagram using Node.js. I hope you liked it.

Wait, what? Ah… you want to know why I made you install ffmpeg? Ok sure, let’s talk about it.

Making sure the video is Instagram-ready

Everything is good and the code is nice and dandy, but there’s one more thing we need to do before we can post our video to Instagram. We need to make sure the video is in the right format. Instagram has some requirements for videos, and we need to make sure our video meets these requirements. For that, we’ll use ffmpeg.

There are two very nice NPM packages that allow us to use ffmpeg in Node.js: fluent-ffmpeg and ffmpeg-static. The first one is a wrapper around ffmpeg, and the second one is a static build of ffmpeg that we can use in our code.

const processVideo = (inputPath, outputPath) => {
  return new Promise((resolve, reject) => {
      .on('end', () => resolve(`Processing finished. Output saved to ${outputPath}`))
      .on('error', (err) => reject(new Error(`Error processing video: ${err.message}`)))

This code will make sure that the video is in the right format. It will convert the video to mp4, resize it to 1080x1920, and make sure the aspect ratio is 9:16. It will also make sure the video codec is libx264, and the audio codec is aac. It will also make sure the audio has 2 channels, a frequency of 48000, and a bitrate of 128k.


And that’s it, with a little bit of code and trickery, we can now post videos to Instagram using Node.js. Was it worth the hours I’ve spent researching and coding this? Probably not, but I had fun, and I learned a lot, so I guess it was worth it then. Uhmn…

Ah, and don’t forget to follow me on Instagram at @thepiratepablo and help me become an influencer. I promise I’ll post often. I’m just a bit lazy, but I’ll get there.

I hope you liked it and that this was useful to you somehow. See you next time! Cheers!


Post a comment


No comments yet.