Introduction
In this tutorial, we'll walk you through creating a quote image generator for Instagram using Node.js and the Pictify API. This tool will allow you to automatically generate visually appealing quote images, perfect for engaging your Instagram audience and maintaining a consistent aesthetic on your profile.
Why build a quote image generator?
- Engagement: Quote posts often receive high engagement on Instagram.
- Consistency: Maintain a cohesive visual style across your quote posts.
- Time-saving: Automate the process of creating quote images.
- Customization: Easily adapt quotes to fit your brand's style.
Prerequisites
Before we begin, ensure you have:
- Node.js installed on your machine
- A Pictify API key (sign up at pictify.io)
- A unsplash API key (sign up at unsplash.com)
- Basic knowledge of JavaScript and Node.js
Step 1: Project Setup
Create a new directory for your project and initialize it:
mkdir instagram-quote-generator
cd instagram-quote-generator
npm init -yInstall the required packages:
npm install axios ejsCreate a new file quoteGenerator.js and add the following boilerplate:
const axios = require('axios');
const ejs = require('ejs');
const fs = require('fs');
const apiKey = 'YOUR_PICTIFY_API_KEY';
// We'll add our functions hereStep 2: Create the Image Template
Create a new file quoteTemplate.ejs with the following content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quote Image</title>
<style>
body {
margin: 0;
padding: 0;
width: 1080px;
height: 1080px;
display: flex;
justify-content: center;
align-items: center;
background-image: url('<%= backgroundImage %>');
background-size: cover;
font-family: Arial, sans-serif;
}
.quote-container {
background-color: rgba(0, 0, 0, 0.5);
padding: 40px;
border-radius: 10px;
text-align: center;
color: white;
}
.quote-text {
font-size: 36px;
font-weight: bold;
margin-bottom: 20px;
}
.quote-author {
font-size: 24px;
font-style: italic;
}
</style>
</head>
<body>
<div class="quote-container">
<div class="quote-text">"<%= quoteText %>"</div>
<div class="quote-author">- <%= quoteAuthor %></div>
</div>
</body>
</html>Step 3: Generate Quote Content
Add a function to fetch a random quote. For this example, we'll use the Quotable API:
async function getRandomQuote() {
try {
const response = await axios.get('https://api.quotable.io/random?');
return {
text: response.data.content,
author: response.data.author
};
} catch (error) {
console.error('Error fetching quote:', error);
return null;
}
}Step 4: Generate the Quote Image
Add a function to generate the quote image using Pictify API:
async function generateQuoteImage(quote) {
try {
const imageResponse = await axios.get('https://api.unsplash.com/photos/random?client_id=YOUR_UNSPLASH_API_KEY&query=nature&orientation=landscape');
const backgroundImage = imageResponse.data.urls.full;
const html = await ejs.renderFile('quoteTemplate.ejs', {
quoteText: quote.text,
quoteAuthor: quote.author,
backgroundImage
});
const response = await axios.post('https://api.pictify.io/v1/image', {
html,
width: 1080,
height: 1080
}, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return response.data.url;
} catch (error) {
console.error('Error generating image:', error);
return null;
}
}Step 5: Put It All Together
Add a main function to run the quote image generator:
async function main() {
const quote = await getRandomQuote();
if (quote) {
const imageUrl = await generateQuoteImage(quote);
if (imageUrl) {
console.log('Quote image generated:', imageUrl);
// Here you could add code to save the image or post it to Instagram
}
}
}
main();Running the Generator
To run the quote image generator, execute:
node quoteGenerator.jsExample output:
Quote image generated: https://media.pictify.io/u5sw1-1723140865874.png
This will output a URL to your generated quote image, which you can then download or use in your Instagram posts.
Step 6: Adding Multiple Visual Themes
A single static design gets stale fast if you're posting daily. Instead of one quoteTemplate.ejs, maintain a small set of templates and pick one per quote so your feed doesn't look like the same card recolored.
Create three more templates alongside the original:
quoteTemplateMinimal.ejs: plain background color, no photo, large centered typequoteTemplateBold.ejs: solid brand color background, oversized quote text, no author photo overlayquoteTemplateGradient.ejs: CSS gradient background instead of an Unsplash photo, useful when you want zero external image dependency
Each one takes the same quoteText / quoteAuthor variables as the original; only the CSS and background source change. A gradient variant, for example, drops the backgroundImage param entirely:
<!-- quoteTemplateGradient.ejs -->
<body style="margin:0;padding:0;width:1080px;height:1080px;display:flex;justify-content:center;align-items:center;background:linear-gradient(135deg, <%= gradientFrom %>, <%= gradientTo %>);font-family:Arial, sans-serif;">
<div style="padding:60px;text-align:center;color:white;max-width:800px;">
<div style="font-size:42px;font-weight:bold;margin-bottom:24px;">"<%= quoteText %>"</div>
<div style="font-size:24px;font-style:italic;opacity:0.85;">- <%= quoteAuthor %></div>
</div>
</body>Now map template names to files and pick one, either at random or on a rule (e.g. rotate by day of week so Mondays always look the same way):
const TEMPLATES = {
photo: { file: 'quoteTemplate.ejs', needsImage: true },
minimal: { file: 'quoteTemplateMinimal.ejs', needsImage: false },
bold: { file: 'quoteTemplateBold.ejs', needsImage: false },
gradient: { file: 'quoteTemplateGradient.ejs', needsImage: false }
};
const GRADIENTS = [
{ from: '#ff6b6b', to: '#feca57' },
{ from: '#5f27cd', to: '#00d2d3' },
{ from: '#0abde3', to: '#10ac84' }
];
function pickTemplate() {
const names = Object.keys(TEMPLATES);
// Rotate by day of week instead of pure random if you want a
// predictable weekly rhythm, e.g. names[new Date().getDay() % names.length]
return names[Math.floor(Math.random() * names.length)];
}
async function generateQuoteImage(quote, templateName = pickTemplate()) {
const template = TEMPLATES[templateName];
try {
const renderVars = { quoteText: quote.text, quoteAuthor: quote.author };
if (template.needsImage) {
const imageResponse = await axios.get('https://api.unsplash.com/photos/random?client_id=YOUR_UNSPLASH_API_KEY&query=nature&orientation=landscape');
renderVars.backgroundImage = imageResponse.data.urls.full;
} else if (templateName === 'gradient') {
const gradient = GRADIENTS[Math.floor(Math.random() * GRADIENTS.length)];
renderVars.gradientFrom = gradient.from;
renderVars.gradientTo = gradient.to;
}
const html = await ejs.renderFile(template.file, renderVars);
const response = await axios.post('https://api.pictify.io/v1/image', {
html,
width: 1080,
height: 1080
}, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return response.data.url;
} catch (error) {
console.error('Error generating image:', error);
return null;
}
}This replaces the generateQuoteImage function from Step 4: same signature, but now template-aware. Everything downstream (Step 5's main()) keeps working unchanged, since templateName defaults to a random pick when you don't pass one.
Step 7: Batch-Generating a Week's Worth of Quotes
Once you're managing a content calendar rather than posting one-off, generating images one at a time in a loop each morning gets tedious. Generate a week's batch in a single run instead.
async function generateBatch(count = 7) {
const results = [];
for (let i = 0; i < count; i++) {
const quote = await getRandomQuote();
if (!quote) continue;
const templateName = pickTemplate();
const imageUrl = await generateQuoteImage(quote, templateName);
if (!imageUrl) continue;
results.push({
day: i + 1,
template: templateName,
quoteText: quote.text,
quoteAuthor: quote.author,
imageUrl
});
console.log(`Day ${i + 1}/${count} generated (${templateName}):`, imageUrl);
}
fs.writeFileSync('quote-batch.json', JSON.stringify(results, null, 2));
return results;
}
generateBatch(7);This runs sequentially with await inside the loop rather than firing all seven requests via Promise.all; both the Quotable API and Unsplash's free tier rate-limit aggressively, and a sequential loop is easier to reason about when one request in the middle fails. If you're generating from your own quote list instead of a random API, Promise.all over a fixed array is safe and considerably faster:
async function generateFromList(quotes) {
const jobs = quotes.map((quote, i) =>
generateQuoteImage(quote, pickTemplate()).then((imageUrl) => ({
day: i + 1,
quoteText: quote.text,
quoteAuthor: quote.author,
imageUrl
}))
);
return Promise.all(jobs);
}The output JSON (quote-batch.json) is the handoff point to whatever you use for scheduling: each entry has a ready-to-use image URL plus the quote metadata, indexed by day.
Step 8: Auto-Posting to Instagram
Generating the images is half the job; the other half is getting them onto your feed without opening Instagram seven times a week. Three practical options, roughly in order of setup effort:
Scheduling tools (Buffer, Later). Both accept an image URL via their API and let you queue a post for a specific time. Since generateBatch() above already returns a plain image URL per quote, wiring it into Buffer's API is a short loop:
async function scheduleToBuffer(batch, profileId, bufferAccessToken) {
for (const post of batch) {
await axios.post('https://api.bufferapp.com/1/updates/create.json', {
profile_ids: [profileId],
media: { photo: post.imageUrl },
text: `"${post.quoteText}" - ${post.quoteAuthor}`
}, {
headers: { 'Authorization': `Bearer ${bufferAccessToken}` }
});
}
}Meta's Graph API directly. More setup (a Meta developer app, an Instagram Business account, a long-lived access token) but no third-party dependency. The flow is a two-step publish: create a media container from the image URL, then publish that container.
A cron job that just calls generateBatch() and stops there. If you'd rather review images before they go live, the simplest reliable pattern is: cron generates the week's batch into quote-batch.json every Sunday night, you glance at it Monday morning, and post manually. Automation doesn't have to mean zero human review; for a brand account, it often shouldn't.
Conclusion
You've now created a powerful tool for generating Instagram quote images using Node.js and the Pictify API, one that supports multiple visual themes, can batch-generate a week of content in one run, and has a clear path to scheduled auto-posting. This generator can be further customized to fit your brand's style by adjusting the EJS templates, using specific fonts, or integrating with your own quote database.
To extend this project further, consider:
- Pulling quotes from your own curated database instead of the random Quotable API, so every quote is on-brand
- Adding a review step (Slack notification, simple web UI) between batch generation and scheduling
- A/B testing which template style gets the most engagement, then weighting
pickTemplate()toward the winner
Happy quote posting!
Ship documents, images and video from one template.
50 renders a month on the free tier. No card, no watermark.
Start free