🧠 Smart Unfollow
Intelligently unfollow users who haven't followed you back after a configurable grace period.
Author: nich (@nichxbt)
📖 What It Does
Smart Unfollow is an intelligent unfollowing system that:
- Tracks when you follow someone - Maintains a log of all follow actions with timestamps
- Respects a grace period - Gives users X days to follow you back before considering them for unfollow
- Identifies non-reciprocal follows - Finds users who haven't followed back after the grace period expires
- Safely unfollows - Removes these users with rate limiting and detailed logging
- Supports dry-run mode - Preview what would happen without making actual changes
This approach is smarter than a simple "unfollow non-followers" script because it accounts for the fact that people need time to see your follow and decide to follow back.
⚙️ Example 1: Configuration Approach
The simplest way to use Smart Unfollow is through the xactions configuration:
const { xactions } = require('xactions');
const client = new xactions({
username: process.env.X_USERNAME,
password: process.env.X_PASSWORD,
});
await client.smartUnfollow({
gracePeriodDays: 3, // Wait 3 days before unfollowing
maxUnfollows: 50, // Limit per session
dryRun: false, // Set to true to preview
excludeVerified: true, // Don't unfollow verified accounts
excludeList: ['nichxbt'], // Never unfollow these users
});
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
gracePeriodDays |
number | 3 | Days to wait before unfollowing |
maxUnfollows |
number | 100 | Maximum unfollows per session |
dryRun |
boolean | false | Preview mode without actual unfollows |
excludeVerified |
boolean | true | Skip verified accounts |
excludeList |
string[] | [] | Usernames to never unfollow |
logFile |
string | ./follow_log.json |
Path to the follow tracking log |
🚀 Example 2: Node.js with Puppeteer
Here's a complete, production-ready implementation:
Project Structure
smart-unfollow/
├── smart-unfollow.js # Main script
├── follow_log.json # Auto-generated follow tracking
├── unfollow_log.json # Auto-generated unfollow history
├── .env # Your credentials
└── package.json
package.json
{
"name": "smart-unfollow",
"version": "1.0.0",
"description": "Intelligently unfollow non-followers after grace period",
"main": "smart-unfollow.js",
"scripts": {
"start": "node smart-unfollow.js",
"dry-run": "node smart-unfollow.js --dry-run",
"log-follow": "node smart-unfollow.js --log-follow"
},
"dependencies": {
"puppeteer": "^21.0.0",
"dotenv": "^16.0.0"
}
}
.env
X_USERNAME=your_username
X_PASSWORD=your_password
X_EMAIL=your_email@example.com
GRACE_PERIOD_DAYS=3
MAX_UNFOLLOWS_PER_SESSION=50
smart-unfollow.js
/**
* Smart Unfollow - Intelligent Non-Follower Removal
*
* Unfollows users who haven't followed back after a configurable grace period.
* Maintains persistent logs to track follow dates and unfollow history.
*
* @author nich (@nichxbt)
* @license MIT
*/
require('dotenv').config();
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
// ============================================================================
// CONFIGURATION
// ============================================================================
const CONFIG = {
// Credentials
username: process.env.X_USERNAME,
password: process.env.X_PASSWORD,
email: process.env.X_EMAIL,
// Grace period settings
gracePeriodDays: parseInt(process.env.GRACE_PERIOD_DAYS) || 3,
maxUnfollowsPerSession: parseInt(process.env.MAX_UNFOLLOWS_PER_SESSION) || 50,
// File paths
followLogPath: path.join(__dirname, 'follow_log.json'),
unfollowLogPath: path.join(__dirname, 'unfollow_log.json'),
// Rate limiting (milliseconds)
delayBetweenActions: 3000,
delayBetweenScrolls: 2000,
pageLoadDelay: 5000,
// Browser settings
headless: false,
slowMo: 50,
// Safety settings
excludeVerified: true,
excludeUsernames: ['nichxbt', 'elonmusk'], // Never unfollow these
// Selectors (may need updates if X changes their UI)
selectors: {
usernameInput: 'input[autocomplete="username"]',
passwordInput: 'input[name="password"]',
nextButton: '[role="button"]:has-text("Next")',
loginButton: '[data-testid="LoginForm_Login_Button"]',
followingLink: 'a[href$="/following"]',
userCell: '[data-testid="UserCell"]',
unfollowButton: '[data-testid="placementTracking"] [role="button"]',
confirmUnfollow: '[data-testid="confirmationSheetConfirm"]',
verifiedBadge: 'svg[aria-label="Verified account"]',
},
};
// ============================================================================
// LOGGING UTILITIES
// ============================================================================
class Logger {
static colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
};
static timestamp() {
return new Date().toISOString();
}
static log(message, color = 'reset') {
console.log(`${this.colors[color]}[${this.timestamp()}] ${message}${this.colors.reset}`);
}
static info(message) { this.log(`ℹ️ ${message}`, 'blue'); }
static success(message) { this.log(`✅ ${message}`, 'green'); }
static warning(message) { this.log(`⚠️ ${message}`, 'yellow'); }
static error(message) { this.log(`❌ ${message}`, 'red'); }
static action(message) { this.log(`🔄 ${message}`, 'cyan'); }
static dry(message) { this.log(`🧪 [DRY-RUN] ${message}`, 'magenta'); }
}
// ============================================================================
// FOLLOW LOG MANAGEMENT
// ============================================================================
class FollowLog {
constructor(filePath) {
this.filePath = filePath;
this.data = this.load();
}
load() {
try {
if (fs.existsSync(this.filePath)) {
const content = fs.readFileSync(this.filePath, 'utf8');
return JSON.parse(content);
}
} catch (error) {
Logger.warning(`Could not load follow log: ${error.message}`);
}
return { follows: {}, metadata: { created: new Date().toISOString() } };
}
save() {
try {
this.data.metadata.lastUpdated = new Date().toISOString();
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
Logger.success(`Follow log saved with ${Object.keys(this.data.follows).length} entries`);
} catch (error) {
Logger.error(`Could not save follow log: ${error.message}`);
}
}
addFollow(username) {
const normalizedUsername = username.toLowerCase().replace('@', '');
if (!this.data.follows[normalizedUsername]) {
this.data.follows[normalizedUsername] = {
username: normalizedUsername,
followedAt: new Date().toISOString(),
status: 'following',
};
Logger.success(`Logged follow: @${normalizedUsername}`);
return true;
}
Logger.warning(`@${normalizedUsername} already in log`);
return false;
}
markUnfollowed(username, reason = 'manual') {
const normalizedUsername = username.toLowerCase().replace('@', '');
if (this.data.follows[normalizedUsername]) {
this.data.follows[normalizedUsername].status = 'unfollowed';
this.data.follows[normalizedUsername].unfollowedAt = new Date().toISOString();
this.data.follows[normalizedUsername].unfollowReason = reason;
}
}
getExpiredNonFollowers(gracePeriodDays) {
const now = new Date();
const expired = [];
for (const [username, entry] of Object.entries(this.data.follows)) {
if (entry.status !== 'following') continue;
const followedAt = new Date(entry.followedAt);
const daysSinceFollow = (now - followedAt) / (1000 * 60 * 60 * 24);
if (daysSinceFollow >= gracePeriodDays) {
expired.push({
username,
followedAt: entry.followedAt,
daysSinceFollow: Math.floor(daysSinceFollow),
});
}
}
return expired.sort((a, b) => b.daysSinceFollow - a.daysSinceFollow);
}
getStats() {
const follows = Object.values(this.data.follows);
return {
total: follows.length,
following: follows.filter(f => f.status === 'following').length,
unfollowed: follows.filter(f => f.status === 'unfollowed').length,
};
}
}
// ============================================================================
// UNFOLLOW LOG MANAGEMENT
// ============================================================================
class UnfollowLog {
constructor(filePath) {
this.filePath = filePath;
this.data = this.load();
}
load() {
try {
if (fs.existsSync(this.filePath)) {
return JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
}
} catch (error) {
Logger.warning(`Could not load unfollow log: ${error.message}`);
}
return { sessions: [], totalUnfollowed: 0 };
}
save() {
try {
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
} catch (error) {
Logger.error(`Could not save unfollow log: ${error.message}`);
}
}
logSession(unfollowed, dryRun = false) {
const session = {
timestamp: new Date().toISOString(),
dryRun,
count: unfollowed.length,
users: unfollowed,
};
this.data.sessions.push(session);
if (!dryRun) {
this.data.totalUnfollowed += unfollowed.length;
}
this.save();
}
}
// ============================================================================
// SMART UNFOLLOW ENGINE
// ============================================================================
class SmartUnfollower {
constructor(config) {
this.config = config;
this.browser = null;
this.page = null;
this.followLog = new FollowLog(config.followLogPath);
this.unfollowLog = new UnfollowLog(config.unfollowLogPath);
this.sessionUnfollowed = [];
}
async initialize() {
Logger.info('Launching browser...');
this.browser = await puppeteer.launch({
headless: this.config.headless,
slowMo: this.config.slowMo,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'--window-size=1280,800',
],
});
this.page = await this.browser.newPage();
await this.page.setViewport({ width: 1280, height: 800 });
await this.page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
);
Logger.success('Browser initialized');
}
async login() {
Logger.action('Navigating to X login...');
await this.page.goto('https://x.com/i/flow/login', {
waitUntil: 'networkidle2',
timeout: 60000
});
await this.delay(this.config.pageLoadDelay);
// Enter username
Logger.action('Entering username...');
await this.page.waitForSelector(this.config.selectors.usernameInput, { timeout: 30000 });
await this.page.type(this.config.selectors.usernameInput, this.config.username, { delay: 100 });
// Click Next
await this.page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll('[role="button"]'));
const nextButton = buttons.find(b => b.textContent.includes('Next'));
if (nextButton) nextButton.click();
});
await this.delay(3000);
// Check for email verification challenge
const emailInput = await this.page.$('input[data-testid="ocfEnterTextTextInput"]');
if (emailInput) {
Logger.warning('Email verification required...');
await emailInput.type(this.config.email, { delay: 100 });
await this.page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll('[role="button"]'));
const nextButton = buttons.find(b => b.textContent.includes('Next'));
if (nextButton) nextButton.click();
});
await this.delay(3000);
}
// Enter password
Logger.action('Entering password...');
await this.page.waitForSelector(this.config.selectors.passwordInput, { timeout: 30000 });
await this.page.type(this.config.selectors.passwordInput, this.config.password, { delay: 100 });
// Click Login
await this.page.waitForSelector(this.config.selectors.loginButton, { timeout: 10000 });
await this.page.click(this.config.selectors.loginButton);
await this.delay(this.config.pageLoadDelay);
Logger.success('Login successful');
}
async getFollowers() {
Logger.action('Fetching your followers list...');
await this.page.goto(`https://x.com/${this.config.username}/followers`, {
waitUntil: 'networkidle2',
timeout: 60000,
});
await this.delay(this.config.pageLoadDelay);
const followers = new Set();
let previousSize = 0;
let scrollAttempts = 0;
const maxScrollAttempts = 50;
while (scrollAttempts < maxScrollAttempts) {
// Extract usernames from current view
const usernames = await this.page.evaluate(() => {
const cells = document.querySelectorAll('[data-testid="UserCell"]');
return Array.from(cells).map(cell => {
const link = cell.querySelector('a[href^="/"]');
if (link) {
const href = link.getAttribute('href');
return href.replace('/', '').toLowerCase();
}
return null;
}).filter(Boolean);
});
usernames.forEach(u => followers.add(u));
// Check if we got new followers
if (followers.size === previousSize) {
scrollAttempts++;
} else {
scrollAttempts = 0;
previousSize = followers.size;
}
// Scroll down
await this.page.evaluate(() => window.scrollBy(0, 800));
await this.delay(this.config.delayBetweenScrolls);
Logger.info(`Found ${followers.size} followers so far...`);
// Stop if no new followers after several attempts
if (scrollAttempts >= 5) break;
}
Logger.success(`Total followers found: ${followers.size}`);
return followers;
}
async getFollowing() {
Logger.action('Fetching your following list...');
await this.page.goto(`https://x.com/${this.config.username}/following`, {
waitUntil: 'networkidle2',
timeout: 60000,
});
await this.delay(this.config.pageLoadDelay);
const following = new Map(); // username -> { verified, displayName }
let previousSize = 0;
let scrollAttempts = 0;
const maxScrollAttempts = 50;
while (scrollAttempts < maxScrollAttempts) {
// Extract user info from current view
const users = await this.page.evaluate(() => {
const cells = document.querySelectorAll('[data-testid="UserCell"]');
return Array.from(cells).map(cell => {
const link = cell.querySelector('a[href^="/"]');
const verified = cell.querySelector('svg[aria-label*="Verified"]') !== null;
const displayNameEl = cell.querySelector('[dir="ltr"] > span');
if (link) {
const href = link.getAttribute('href');
return {
username: href.replace('/', '').toLowerCase(),
verified,
displayName: displayNameEl ? displayNameEl.textContent : '',
};
}
return null;
}).filter(Boolean);
});
users.forEach(u => following.set(u.username, u));
if (following.size === previousSize) {
scrollAttempts++;
} else {
scrollAttempts = 0;
previousSize = following.size;
}
await this.page.evaluate(() => window.scrollBy(0, 800));
await this.delay(this.config.delayBetweenScrolls);
Logger.info(`Found ${following.size} following so far...`);
if (scrollAttempts >= 5) break;
}
Logger.success(`Total following found: ${following.size}`);
return following;
}
async unfollowUser(username) {
try {
await this.page.goto(`https://x.com/${username}`, {
waitUntil: 'networkidle2',
timeout: 30000,
});
await this.delay(2000);
// Find and click the Following button (which will show unfollow option)
const unfollowed = await this.page.evaluate(() => {
// Look for the "Following" button
const buttons = Array.from(document.querySelectorAll('[role="button"]'));
const followingBtn = buttons.find(b => {
const text = b.textContent || '';
return text.includes('Following') && !text.includes('Followers');
});
if (followingBtn) {
followingBtn.click();
return true;
}
return false;
});
if (!unfollowed) {
Logger.warning(`Could not find Following button for @${username}`);
return false;
}
await this.delay(1000);
// Confirm unfollow in the dialog
await this.page.evaluate(() => {
const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
if (confirmBtn) confirmBtn.click();
});
await this.delay(1000);
return true;
} catch (error) {
Logger.error(`Failed to unfollow @${username}: ${error.message}`);
return false;
}
}
async runSmartUnfollow(dryRun = false) {
Logger.info('='.repeat(60));
Logger.info('🧠 SMART UNFOLLOW - Starting Session');
Logger.info(` Grace Period: ${this.config.gracePeriodDays} days`);
Logger.info(` Max Unfollows: ${this.config.maxUnfollowsPerSession}`);
Logger.info(` Dry Run: ${dryRun ? 'YES' : 'NO'}`);
Logger.info('='.repeat(60));
await this.initialize();
try {
await this.login();
// Get current followers and following
const followers = await this.getFollowers();
const following = await this.getFollowing();
// Find non-followers
const nonFollowers = [];
for (const [username, userData] of following) {
if (!followers.has(username)) {
nonFollowers.push({ username, ...userData });
}
}
Logger.info(`Found ${nonFollowers.length} non-followers out of ${following.size} following`);
// Get expired entries from our log
const expiredFromLog = this.followLog.getExpiredNonFollowers(this.config.gracePeriodDays);
Logger.info(`Found ${expiredFromLog.length} users past grace period in follow log`);
// Cross-reference: only unfollow if they're in our log AND still non-followers
const toUnfollow = [];
for (const entry of expiredFromLog) {
const nonFollower = nonFollowers.find(nf => nf.username === entry.username);
if (nonFollower) {
// Check exclusions
if (this.config.excludeUsernames.includes(entry.username)) {
Logger.warning(`Skipping @${entry.username} (in exclude list)`);
continue;
}
if (this.config.excludeVerified && nonFollower.verified) {
Logger.warning(`Skipping @${entry.username} (verified account)`);
continue;
}
toUnfollow.push({
...entry,
verified: nonFollower.verified,
});
}
}
Logger.info('='.repeat(60));
Logger.info(`📋 UNFOLLOW CANDIDATES: ${toUnfollow.length}`);
Logger.info('='.repeat(60));
if (toUnfollow.length === 0) {
Logger.success('No users to unfollow! Everyone is either:');
Logger.info(' - Following you back');
Logger.info(' - Still within grace period');
Logger.info(' - In your exclude list');
return;
}
// Limit to max per session
const batch = toUnfollow.slice(0, this.config.maxUnfollowsPerSession);
Logger.info(`Processing ${batch.length} users (limited to ${this.config.maxUnfollowsPerSession} per session)`);
Logger.info('');
for (const user of batch) {
Logger.info(`@${user.username} - followed ${user.daysSinceFollow} days ago`);
}
Logger.info('');
if (dryRun) {
Logger.dry('='.repeat(60));
Logger.dry('DRY RUN COMPLETE - No actual unfollows performed');
Logger.dry(`Would have unfollowed ${batch.length} users`);
Logger.dry('='.repeat(60));
this.unfollowLog.logSession(batch.map(u => u.username), true);
return;
}
// Perform actual unfollows
Logger.action('Starting unfollow process...');
let successCount = 0;
for (let i = 0; i < batch.length; i++) {
const user = batch[i];
Logger.action(`[${i + 1}/${batch.length}] Unfollowing @${user.username}...`);
const success = await this.unfollowUser(user.username);
if (success) {
successCount++;
this.sessionUnfollowed.push(user.username);
this.followLog.markUnfollowed(user.username, 'smart-unfollow');
Logger.success(`Unfollowed @${user.username}`);
} else {
Logger.error(`Failed to unfollow @${user.username}`);
}
// Rate limiting
if (i < batch.length - 1) {
Logger.info(`Waiting ${this.config.delayBetweenActions / 1000}s before next action...`);
await this.delay(this.config.delayBetweenActions);
}
}
// Save logs
this.followLog.save();
this.unfollowLog.logSession(this.sessionUnfollowed, false);
// Summary
Logger.info('='.repeat(60));
Logger.success('SESSION COMPLETE');
Logger.info(` Attempted: ${batch.length}`);
Logger.info(` Successful: ${successCount}`);
Logger.info(` Failed: ${batch.length - successCount}`);
Logger.info('='.repeat(60));
} catch (error) {
Logger.error(`Session error: ${error.message}`);
throw error;
} finally {
await this.cleanup();
}
}
async logNewFollow(username) {
const added = this.followLog.addFollow(username);
if (added) {
this.followLog.save();
}
const stats = this.followLog.getStats();
Logger.info(`Follow log stats: ${stats.following} following, ${stats.unfollowed} unfollowed`);
}
async showStats() {
const stats = this.followLog.getStats();
const expired = this.followLog.getExpiredNonFollowers(this.config.gracePeriodDays);
Logger.info('='.repeat(60));
Logger.info('📊 SMART UNFOLLOW STATISTICS');
Logger.info('='.repeat(60));
Logger.info(`Total logged follows: ${stats.total}`);
Logger.info(`Currently following: ${stats.following}`);
Logger.info(`Unfollowed: ${stats.unfollowed}`);
Logger.info(`Past grace period: ${expired.length}`);
Logger.info(`Grace period: ${this.config.gracePeriodDays} days`);
Logger.info('='.repeat(60));
if (expired.length > 0) {
Logger.info('Users past grace period:');
expired.slice(0, 10).forEach(u => {
Logger.info(` @${u.username} - ${u.daysSinceFollow} days ago`);
});
if (expired.length > 10) {
Logger.info(` ... and ${expired.length - 10} more`);
}
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async cleanup() {
if (this.browser) {
await this.browser.close();
Logger.info('Browser closed');
}
}
}
// ============================================================================
// CLI INTERFACE
// ============================================================================
async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const logFollow = args.includes('--log-follow');
const showStats = args.includes('--stats');
const username = args.find(a => !a.startsWith('--'));
const unfollower = new SmartUnfollower(CONFIG);
try {
if (showStats) {
await unfollower.showStats();
} else if (logFollow && username) {
await unfollower.logNewFollow(username);
} else if (logFollow) {
Logger.error('Usage: node smart-unfollow.js --log-follow <username>');
process.exit(1);
} else {
await unfollower.runSmartUnfollow(dryRun);
}
} catch (error) {
Logger.error(`Fatal error: ${error.message}`);
console.error(error);
process.exit(1);
}
}
main();
Usage
# Install dependencies
npm install
# Run dry-run first to see what would be unfollowed
npm run dry-run
# Run actual unfollow
npm start
# Log a new follow (when you follow someone)
node smart-unfollow.js --log-follow username123
# View statistics
node smart-unfollow.js --stats
Sample Output
[2026-01-01T12:00:00.000Z] ℹ️ ============================================================
[2026-01-01T12:00:00.000Z] ℹ️ 🧠 SMART UNFOLLOW - Starting Session
[2026-01-01T12:00:00.000Z] ℹ️ Grace Period: 3 days
[2026-01-01T12:00:00.000Z] ℹ️ Max Unfollows: 50
[2026-01-01T12:00:00.000Z] ℹ️ Dry Run: NO
[2026-01-01T12:00:00.000Z] ℹ️ ============================================================
[2026-01-01T12:00:01.000Z] ✅ Browser initialized
[2026-01-01T12:00:05.000Z] ✅ Login successful
[2026-01-01T12:00:30.000Z] ✅ Total followers found: 1,523
[2026-01-01T12:00:55.000Z] ✅ Total following found: 892
[2026-01-01T12:00:55.000Z] ℹ️ Found 156 non-followers out of 892 following
[2026-01-01T12:00:55.000Z] ℹ️ Found 23 users past grace period in follow log
[2026-01-01T12:00:56.000Z] 🔄 [1/23] Unfollowing @inactive_user...
[2026-01-01T12:00:58.000Z] ✅ Unfollowed @inactive_user
...
⏰ Why Grace Period Matters
The Problem with Instant Unfollowing
When you follow someone, they might not see your follow immediately:
- They could be offline or busy
- They might have notifications turned off
- They could be in a different timezone
- They might check Twitter/X only weekly
- Your follow could get lost in a sea of notifications
The Grace Period Solution
A grace period (typically 3-7 days) gives people fair time to:
- See your follow notification - Not everyone checks social media daily
- Check out your profile - They need time to review your content
- Make a decision - Following back isn't always instant
- Actually follow back - Technical issues happen
Recommended Grace Periods
| Audience Type | Recommended Grace Period |
|---|---|
| Active users | 3 days |
| General audience | 5-7 days |
| Business/Professional | 7-14 days |
| Celebrities/Influencers | Never (don't expect follow-back) |
Best Practices
- Start with 7 days - This is a fair window for most users
- Track your metrics - See what grace period works for your audience
- Adjust based on results - If many people follow back on day 5-7, extend it
- Consider time zones - International audiences may need more time
- Quality over quantity - Don't rush the unfollow process
🌐 Website Alternative
Don't want to run code? Use the web dashboard:
xactions.app
The xactions.app dashboard provides:
- Visual Smart Unfollow - Same functionality with a beautiful UI
- Follow Tracking - Automatic logging of when you follow users
- Grace Period Settings - Customize your waiting period
- Exclude Lists - Protect important accounts
- Scheduling - Set up automated smart unfollow sessions
- Analytics - Track your follow/unfollow ratio over time
- No Code Required - Just connect and configure
Getting Started with xactions.app
- Visit xactions.app
- Connect your X account
- Navigate to Tools → Smart Unfollow
- Configure your grace period and exclusions
- Review candidates and start unfollowing
🔒 Safety & Best Practices
- Always use dry-run first - Preview before taking action
- Set reasonable limits - Don't unfollow too many at once
- Maintain your follow log - The system is only as good as your data
- Exclude important accounts - Protect business relationships
- Respect rate limits - Don't get your account restricted
- Regular backups - Keep copies of your follow_log.json
📚 Related Documentation
- Unfollow Non-Followers - Simple non-follower removal
- Unfollow Everyone - Mass unfollow tool
- Followers Scraping - Get your followers list
- Following Scraping - Get your following list
Built with ❤️ by @nichxbt
⚡ Ready to try Smart Unfollow?
XActions is 100% free and open-source. No API keys, no fees, no signup.
Browse All Scripts