Automating Pinterest Pins for Static Sites via RSS and Meta Tags
To integrate your site with Pinterest, start by verifying your domain. In Pinterest Business Hub, choose the meta tag method and insert it into the <head> of your homepage:
<meta name="p:domain_verify" content="12ac6ff1eaa70a3386sg439fda58bbb6"/>
This allows Pinterest to index pages with Rich Pins. Next, add meta tags to each article's <head>:
<meta name="is_article" content="true">
<meta name="pinterest_description" content="Pin description, 150–200 characters">
<meta name="pinterest_image" content="https://example.com/image.webp">
<meta name="article_published_at" content="2026-04-03T12:00:00+03:00">
The image should be vertical (1000×1500 px), served via HTTPS, and under 2 MB. Without pinterest_image, Pinterest uses the first <img> on the page, which is often suboptimal.
Generating an RSS Feed with a Node.js Script
Pinterest parses RSS to automatically create pins. Use a script to scan HTML files, extract metadata, and generate rss.xml.
Create generate-rss.js in a scripts folder:
import fs from 'node:fs/promises';
import path from 'node:path';
import * as cheerio from 'cheerio';
// ================== SETTINGS (CUSTOMIZE THESE) ==================
const BASE_URL = 'https://YOUR_SITE.com';
const ROOT_DIR = path.resolve(process.cwd(), 'dist');
const OUTPUT_RSS_PATH = path.join(ROOT_DIR, 'rss.xml');
const CHANNEL_TITLE = 'Your Blog Name';
const CHANNEL_DESCRIPTION = 'Description: articles on development, SEO, design';
// ================== DO NOT MODIFY BELOW THIS LINE ==================
function escapeXml(value = '') {
return value
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function getMimeTypeByExt(urlOrPath = '') {
const ext = urlOrPath.split('.').pop()?.toLowerCase();
switch (ext) {
case 'jpg': case 'jpeg': return 'image/jpeg';
case 'png': return 'image/png';
case 'webp': return 'image/webp';
case 'gif': return 'image/gif';
default: return 'image/*';
}
}
function buildArticleUrl(filePath) {
const relativePath = path.relative(ROOT_DIR, filePath).replace(/\\/g, '/');
return `${BASE_URL}/${relativePath}`;
}
function buildImageUrl(src) {
if (!src) return '';
if (/^https?:\/\//i.test(src)) return src;
if (src.startsWith('/')) return `${BASE_URL}${src}`;
return `${BASE_URL}/${src}`;
}
async function collectHtmlFiles(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
const nested = await collectHtmlFiles(fullPath);
files.push(...nested);
} else if (entry.isFile() && entry.name.endsWith('.html')) {
files.push(fullPath);
}
}
return files;
}
async function parseArticle(filePath) {
const content = await fs.readFile(filePath, 'utf8');
const $ = cheerio.load(content);
const isArticle = $('meta[name="is_article"]').length > 0;
if (!isArticle) return null;
const title = $('h1').first().text().trim() || 'No Title';
const pinterestDescription = $('meta[name="pinterest_description"]').attr('content')?.trim() || '';
const pinterestImg = $('meta[name="pinterest_image"]').attr('content');
let firstImgSrc = $('img').first().attr('src') || '';
if (firstImgSrc.endsWith('.svg')) firstImgSrc = '';
const imageUrl = buildImageUrl(pinterestImg || firstImgSrc);
let pubDate;
const articleDateMeta = $('meta[name="article_published_at"]').attr('content');
if (articleDateMeta) {
const parsed = new Date(articleDateMeta);
if (!isNaN(parsed.getTime())) pubDate = parsed;
}
if (!pubDate) {
const stat = await fs.stat(filePath);
pubDate = stat.mtime;
}
return { title, pinterestDescription, url: buildArticleUrl(filePath), imageUrl, pubDate };
}
function buildRssXml(items) {
const itemsXml = items.map(item => {
const enclosure = item.imageUrl ? `\n <enclosure url="${escapeXml(item.imageUrl)}" type="${getMimeTypeByExt(item.imageUrl)}" />` : '';
return `\n <item>\n <title>${escapeXml(item.title)}</title>\n <link>${escapeXml(item.url)}</link>\n <description><![CDATA[${item.pinterestDescription}]]></description>\n <pubDate>${item.pubDate.toUTCString()}</pubDate>${enclosure}\n </item>`;
}).join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0">\n<channel>\n <title>${escapeXml(CHANNEL_TITLE)}</title>\n <link>${escapeXml(BASE_URL)}</link>\n <description>${escapeXml(CHANNEL_DESCRIPTION)}</description>${itemsXml}\n</channel>\n</rss>`;
}
async function main() {
console.log('🔍 Searching HTML in:', ROOT_DIR);
const htmlFiles = await collectHtmlFiles(ROOT_DIR);
console.log(`📄 HTML files found: ${htmlFiles.length}`);
const articles = [];
for (const filePath of htmlFiles) {
const article = await parseArticle(filePath);
if (article) articles.push(article);
}
console.log(`📰 Articles with is_article: ${articles.length}`);
if (!articles.length) {
console.warn('⚠️ No articles found. RSS not created.');
return;
}
articles.sort((a, b) => b.pubDate - a.pubDate);
const rssXml = buildRssXml(articles);
await fs.writeFile(OUTPUT_RSS_PATH, rssXml, 'utf8');
console.log(`✅ RSS saved: ${OUTPUT_RSS_PATH}`);
}
main().catch(err => {
console.error('❌ Error:', err);
process.exit(1);
});
To run:
npm install cheerio- Configure
BASE_URL,ROOT_DIR, and channel details. node generate-rss.js
The script recursively scans HTML, parses with Cheerio, escapes XML, determines MIME types by extension, and builds absolute URLs. It sorts by pubDate.
Connecting RSS to Pinterest and Automation
In Pinterest: "Create" → "Rich Pins" → "RSS Feed" → paste https://your-site.com/rss.xml. Pins generate within 6–24 hours.
Process:
- Add an article with meta tags.
- Run
node generate-rss.js. - Pinterest parses the RSS and creates pins from
<title>,<description>, and<enclosure>.
For full automation, integrate into CI/CD (e.g., GitHub Actions) or cron: 0 0 * cd /path/to/project && node scripts/generate-rss.js.
Advantages of this approach:
- Works with static sites (Hugo, Eleventy, plain HTML).
- No CMS or plugins required.
- Scales to thousands of articles.
- Pinterest caches RSS, so real-time updates aren't needed.
Key Points
- Verify your domain with the
p:domain_verifymeta tag for indexing. - Use
pinterest_imageto control pin visuals. - The script handles fallbacks to file
mtimeand the first<img>. - RSS includes
<enclosure>for images with MIME types. - Automate via cron or CI for production.
— Editorial Team
No comments yet.