You got 5,000 likes on your last TikTok. Is that good?
If you have 10,000 followers — that's a 50% engagement rate. Incredible.
If you have 2,000,000 followers — that's 0.25%. Terrible.
But even knowing your engagement rate isn't enough. Is 3.2% good for a fitness creator on Instagram with 50K followers? What about a tech creator on TikTok with 200K?
The answer depends on your niche, your platform, and your follower tier. Without benchmarks, you're flying blind.
I built a benchmarker that compares any creator's performance against their actual peer group. Not global averages — niche-specific, tier-specific, platform-specific benchmarks computed from real data. Here's the build.
The Stack
- Node.js – runtime
- SociaVault API – fetch profiles and posts across niches
- Statistics – percentile ranking, z-scores
Why Global Averages Are Useless
Every "engagement rate benchmark" article says the same thing:
"The average Instagram engagement rate is 1.5%."
This is useless because it mixes:
- A food blogger with 5K followers (8% ER) with
- Nike's brand account with 300M followers (0.05% ER)
You need benchmarks that compare apples to apples.
Step 1: Build a Peer Group
const axios = require('axios');
const API_BASE = 'https://api.sociavault.com/v1';
const API_KEY = process.env.SOCIAVAULT_API_KEY;
const api = axios.create({
baseURL: API_BASE,
headers: { 'x-api-key': API_KEY },
});
async function buildPeerGroup(platform, nicheKeywords, followerRange, size = 50) {
const peers = [];
for (const keyword of nicheKeywords) {
const { data } = await api.get(`/${platform}/search/users`, {
params: { keyword, limit: 30 },
});
for (const user of (data.users || [])) {
// Filter by follower range
if (user.followerCount >= followerRange.min && user.followerCount <= followerRange.max) {
peers.push(user);
}
}
}
// Deduplicate
const seen = new Set();
const unique = peers.filter(p => {
if (seen.has(p.username)) return false;
seen.add(p.username);
return true;
});
return unique.slice(0, size);
}
Follower tiers:
const FOLLOWER_TIERS = {
nano: { min: 1000, max: 10000, label: 'Nano (1K-10K)' },
micro: { min: 10000, max: 50000, label: 'Micro (10K-50K)' },
mid: { min: 50000, max: 200000, label: 'Mid-tier (50K-200K)' },
macro: { min: 200000, max: 1000000, label: 'Macro (200K-1M)' },
mega: { min: 1000000, max: Infinity, label: 'Mega (1M+)' },
};
function getTier(followerCount) {
for (const [key, range] of Object.entries(FOLLOWER_TIERS)) {
if (followerCount >= range.min && followerCount < range.max) {
return { key, ...range };
}
}
return { key: 'mega', ...FOLLOWER_TIERS.mega };
}
Step 2: Compute Peer Benchmarks
Fetch detailed metrics for every peer and build statistical benchmarks.
async function computeBenchmarks(platform, peers) {
const peerMetrics = [];
for (const peer of peers) {
try {
const { data: profile } = await api.get(`/${platform}/profile/${peer.username}`);
const { data: postData } = await api.get(`/${platform}/posts/${peer.username}`, {
params: { limit: 30 },
});
const posts = postData.posts;
if (posts.length < 5) continue; // Need enough data
const followers = profile.followerCount;
const engagementRates = posts.map(p =>
((p.likeCount + p.commentCount) / followers) * 100
);
const avgER = engagementRates.reduce((a, b) => a + b, 0) / engagementRates.length;
const avgLikes = posts.reduce((s, p) => s + p.likeCount, 0) / posts.length;
const avgComments = posts.reduce((s, p) => s + p.commentCount, 0) / posts.length;
const avgViews = posts.reduce((s, p) => s + (p.viewCount || 0), 0) / posts.length;
// Comment-to-like ratio (quality signal)
const commentLikeRatio = avgLikes > 0 ? avgComments / avgLikes : 0;
peerMetrics.push({
username: peer.username,
followers,
avgER: parseFloat(avgER.toFixed(2)),
avgLikes: Math.round(avgLikes),
avgComments: Math.round(avgComments),
avgViews: Math.round(avgViews),
commentLikeRatio: parseFloat(commentLikeRatio.toFixed(3)),
postCount: posts.length,
});
} catch (err) {
// Skip failed fetches
}
}
if (peerMetrics.length < 5) {
throw new Error('Not enough peer data. Try broader niche keywords.');
}
// Calculate statistical benchmarks
return {
sampleSize: peerMetrics.length,
benchmarks: {
engagementRate: computeStats(peerMetrics.map(p => p.avgER)),
avgLikes: computeStats(peerMetrics.map(p => p.avgLikes)),
avgComments: computeStats(peerMetrics.map(p => p.avgComments)),
avgViews: computeStats(peerMetrics.map(p => p.avgViews)),
commentLikeRatio: computeStats(peerMetrics.map(p => p.commentLikeRatio)),
},
peers: peerMetrics,
};
}
function computeStats(values) {
const sorted = [...values].sort((a, b) => a - b);
const n = sorted.length;
const mean = sorted.reduce((a, b) => a + b, 0) / n;
const variance = sorted.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / n;
const stdDev = Math.sqrt(variance);
return {
mean: parseFloat(mean.toFixed(2)),
median: parseFloat(sorted[Math.floor(n / 2)].toFixed(2)),
stdDev: parseFloat(stdDev.toFixed(2)),
p25: parseFloat(sorted[Math.floor(n * 0.25)].toFixed(2)),
p75: parseFloat(sorted[Math.floor(n * 0.75)].toFixed(2)),
p90: parseFloat(sorted[Math.floor(n * 0.90)].toFixed(2)),
min: parseFloat(sorted[0].toFixed(2)),
max: parseFloat(sorted[n - 1].toFixed(2)),
};
}
Step 3: Score the Creator Against Benchmarks
function benchmarkCreator(creatorMetrics, benchmarks) {
const results = {};
for (const [metric, stats] of Object.entries(benchmarks.benchmarks)) {
const value = creatorMetrics[metric];
if (value === undefined) continue;
// Percentile rank: what % of peers does this creator beat?
const belowCount = benchmarks.peers.filter(p => {
const peerValue = metric === 'engagementRate' ? p.avgER :
metric === 'avgLikes' ? p.avgLikes :
metric === 'avgComments' ? p.avgComments :
metric === 'avgViews' ? p.avgViews :
p.commentLikeRatio;
return peerValue < value;
}).length;
const percentile = Math.round((belowCount / benchmarks.peers.length) * 100);
// Z-score: how many standard deviations from mean
const zScore = stats.stdDev > 0 ? (value - stats.mean) / stats.stdDev : 0;
// Grade
let grade, emoji;
if (percentile >= 90) { grade = 'A+'; emoji = '🏆'; }
else if (percentile >= 75) { grade = 'A'; emoji = '🟢'; }
else if (percentile >= 50) { grade = 'B'; emoji = '🟡'; }
else if (percentile >= 25) { grade = 'C'; emoji = '🟠'; }
else { grade = 'D'; emoji = '🔴'; }
results[metric] = {
value: parseFloat(value.toFixed(2)),
peerMedian: stats.median,
peerMean: stats.mean,
percentile,
zScore: parseFloat(zScore.toFixed(2)),
grade,
emoji,
};
}
return results;
}
Step 4: Generate the Benchmark Report
async function runBenchmark(platform, username, nicheKeywords) {
// Get creator's data
const { data: profile } = await api.get(`/${platform}/profile/${username}`);
const { data: postData } = await api.get(`/${platform}/posts/${username}`, {
params: { limit: 30 },
});
const posts = postData.posts;
const followers = profile.followerCount;
const tier = getTier(followers);
console.log(`\n═══ PERFORMANCE BENCHMARK REPORT ═══\n`);
console.log(`Creator: @${username} on ${platform}`);
console.log(`Followers: ${followers.toLocaleString()} (${tier.label})`);
console.log(`Niche: ${nicheKeywords.join(', ')}\n`);
// Build peer group in same tier and niche
console.log(`Building peer group (${tier.label}, same niche)...`);
const peers = await buildPeerGroup(platform, nicheKeywords, tier, 50);
console.log(`Found ${peers.length} peers\n`);
// Compute benchmarks
const benchmarkData = await computeBenchmarks(platform, peers);
console.log(`Analyzed ${benchmarkData.sampleSize} peers with sufficient data\n`);
// Calculate creator's metrics
const engagementRates = posts.map(p =>
((p.likeCount + p.commentCount) / followers) * 100
);
const creatorMetrics = {
engagementRate: engagementRates.reduce((a, b) => a + b, 0) / engagementRates.length,
avgLikes: posts.reduce((s, p) => s + p.likeCount, 0) / posts.length,
avgComments: posts.reduce((s, p) => s + p.commentCount, 0) / posts.length,
avgViews: posts.reduce((s, p) => s + (p.viewCount || 0), 0) / posts.length,
commentLikeRatio: posts.reduce((s, p) => s + p.commentCount, 0) /
Math.max(posts.reduce((s, p) => s + p.likeCount, 0), 1),
};
// Score against benchmarks
const scores = benchmarkCreator(creatorMetrics, benchmarkData);
// Print scorecard
console.log('📊 SCORECARD');
console.log('─'.repeat(75));
console.log(
'Metric'.padEnd(22) +
'You'.padStart(10) +
'Peer Median'.padStart(14) +
'Percentile'.padStart(14) +
'Grade'.padStart(10)
);
console.log('─'.repeat(75));
const metricLabels = {
engagementRate: 'Engagement Rate',
avgLikes: 'Avg Likes',
avgComments: 'Avg Comments',
avgViews: 'Avg Views',
commentLikeRatio: 'Comment/Like Ratio',
};
for (const [metric, data] of Object.entries(scores)) {
const label = metricLabels[metric] || metric;
const valueStr = metric === 'engagementRate' ? `${data.value}%` :
metric === 'commentLikeRatio' ? `${data.value}` :
data.value.toLocaleString();
const medianStr = metric === 'engagementRate' ? `${data.peerMedian}%` :
metric === 'commentLikeRatio' ? `${data.peerMedian}` :
data.peerMedian.toLocaleString();
console.log(
`${data.emoji} ${label}`.padEnd(22) +
valueStr.padStart(10) +
medianStr.padStart(14) +
`Top ${100 - data.percentile}%`.padStart(14) +
data.grade.padStart(10)
);
}
console.log('─'.repeat(75));
// Overall grade
const avgPercentile = Object.values(scores).reduce((s, d) => s + d.percentile, 0) / Object.keys(scores).length;
let overallGrade;
if (avgPercentile >= 80) overallGrade = 'A — Outperforming peers';
else if (avgPercentile >= 60) overallGrade = 'B — Above average';
else if (avgPercentile >= 40) overallGrade = 'C — Average';
else overallGrade = 'D — Below peer average';
console.log(`\n🎯 Overall: ${overallGrade} (avg percentile: ${Math.round(avgPercentile)})`);
// Insights
console.log('\n💡 Insights:');
const sorted = Object.entries(scores).sort((a, b) => b[1].percentile - a[1].percentile);
console.log(` Strongest: ${metricLabels[sorted[0][0]]} (top ${100 - sorted[0][1].percentile}% of peers)`);
console.log(` Weakest: ${metricLabels[sorted[sorted.length - 1][0]]} (top ${100 - sorted[sorted.length - 1][1].percentile}% of peers)`);
return { creatorMetrics, scores, benchmarkData };
}
// Run it
runBenchmark('tiktok', 'target_creator', ['fitness', 'workout', 'gym']);
Sample Output
═══ PERFORMANCE BENCHMARK REPORT ═══
Creator: @target_creator on tiktok
Followers: 67,200 (Mid-tier (50K-200K))
Niche: fitness, workout, gym
Building peer group (Mid-tier (50K-200K), same niche)...
Found 43 peers
Analyzed 38 peers with sufficient data
📊 SCORECARD
───────────────────────────────────────────────────────────────────────────
Metric You Peer Median Percentile Grade
───────────────────────────────────────────────────────────────────────────
🟢 Engagement Rate 4.8% 3.2% Top 18% A
🏆 Avg Likes 3,225 2,100 Top 8% A+
🟡 Avg Comments 142 138 Top 45% B
🟠 Avg Views 41,200 52,800 Top 62% C
🟢 Comment/Like Ratio 0.044 0.035 Top 22% A
───────────────────────────────────────────────────────────────────────────
🎯 Overall: B — Above average (avg percentile: 69)
💡 Insights:
Strongest: Avg Likes (top 8% of peers)
Weakest: Avg Views (top 62% of peers)
Engagement rate and likes are excellent. Views are below median — meaning the algorithm isn't distributing the content widely, but people who see it love it. That's a distribution problem, not a content problem.
Read the Full Guide
This is a condensed version. The full guide includes:
- Historical benchmark tracking (are you improving vs. peers?)
- Niche auto-detection from bio and hashtags
- Competitive positioning matrix
- Benchmark API endpoint for product integration
Read the complete guide on SociaVault →
Building analytics tools for creators or brands? SociaVault provides social media data APIs for TikTok, Instagram, YouTube, and 10+ platforms. Search niches, fetch profiles, and compute benchmarks with one unified API.
Discussion
How do you know if your content is actually performing well? Gut feeling? Peer comparison? What benchmarks matter most to you? 👇
Top comments (0)