While basic find-and-replace and sequential numbering handle many file renaming needs, sometimes you need more sophisticated logic. JavaScript-based file renaming opens up unlimited possibilities, from complex conditional logic to data-driven transformations and intelligent pattern recognition.
This guide explores advanced JavaScript techniques for file renaming, specifically designed for tools like Nomio’s Scripting Mode.
Why JavaScript for File Renaming?
JavaScript offers several advantages over traditional renaming methods:
- Conditional Logic: Apply different rules based on file properties
- Complex Transformations: Perform calculations, string manipulation, and data extraction
- Pattern Recognition: Detect and respond to patterns in filenames
- Data-Driven Naming: Generate names based on file metadata
- Reusable Functions: Create utility functions for common tasks
- Error Handling: Gracefully handle edge cases and unexpected inputs
Understanding the Scripting Environment
In Nomio’s Scripting Mode, your JavaScript code has access to:
Available Variables
// Current filename (without extension)
filename // Example: "my-document"
// File extension (without dot)
extension // Example: "pdf"
// Original full filename
originalName // Example: "my-document.pdf"
// File index in the list (0-based)
index // Example: 0, 1, 2, ...
// Total number of files
total // Example: 10
// File size in bytes
size // Example: 1024000
// Last modified date (timestamp)
lastModified // Example: 1696819200000Basic Structure
// Your transformation logic here
// Return the new filename (without extension)
return newFilename;The extension is automatically appended, so you only need to return the base filename.
Foundational Techniques
1. Conditional Renaming Based on File Type
// Different prefixes based on extension
const prefixes = {
'jpg': 'photo',
'jpeg': 'photo',
'png': 'image',
'pdf': 'doc',
'docx': 'doc',
'xlsx': 'spreadsheet',
'mp4': 'video',
'mp3': 'audio'
};
const prefix = prefixes[extension.toLowerCase()] || 'file';
return `${prefix}-${filename}`;Example Results:
vacation.jpg→photo-vacation.jpgreport.pdf→doc-report.pdfdata.xlsx→spreadsheet-data.xlsx
2. Size-Based Categorization
// Categorize by file size
const MB = 1024 * 1024;
let category;
if (size < MB) {
category = 'small';
} else if (size < 10 * MB) {
category = 'medium';
} else if (size < 100 * MB) {
category = 'large';
} else {
category = 'huge';
}
return `${category}-${filename}`;Use Case: Organizing media files by size for storage optimization.
3. Date-Based Naming from Timestamp
// Convert lastModified timestamp to readable date
const date = new Date(lastModified);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}-${filename}`;Example Results:
- Modified on Oct 5, 2024:
2024-10-05-document.pdf
4. Sequential Numbering with Custom Format
// Pad index with zeros based on total files
const digits = String(total).length;
const paddedIndex = String(index + 1).padStart(digits, '0');
return `file-${paddedIndex}-of-${total}`;Example Results (for 100 files):
- File 1:
file-001-of-100.jpg - File 50:
file-050-of-100.jpg - File 100:
file-100-of-100.jpg
Advanced Pattern Recognition
5. Extracting and Reformatting Dates
// Match various date formats and standardize
const datePatterns = [
{ regex: /(\d{4})[-_](\d{2})[-_](\d{2})/, format: (m) => `${m[1]}-${m[2]}-${m[3]}` },
{ regex: /(\d{2})[-_](\d{2})[-_](\d{4})/, format: (m) => `${m[3]}-${m[1]}-${m[2]}` },
{ regex: /(\d{8})/, format: (m) => `${m[1].slice(0,4)}-${m[1].slice(4,6)}-${m[1].slice(6,8)}` }
];
let standardDate = null;
for (const pattern of datePatterns) {
const match = filename.match(pattern.regex);
if (match) {
standardDate = pattern.format(match);
break;
}
}
if (standardDate) {
// Remove old date from filename
const cleanName = filename.replace(/\d{4}[-_]\d{2}[-_]\d{2}|\d{2}[-_]\d{2}[-_]\d{4}|\d{8}/g, '').replace(/^[-_]+|[-_]+$/g, '');
return `${standardDate}-${cleanName}`;
}
return filename;Example Results:
20241005_vacation.jpg→2024-10-05-vacation.jpg05-10-2024_vacation.jpg→2024-05-10-vacation.jpg2024-10-05_vacation.jpg→2024-10-05-vacation.jpg(no change, already standard)
6. Episode Number Extraction
// Extract season and episode from various formats
const patterns = [
/[Ss](\d{1,2})[Ee](\d{1,2})/, // S01E05
/(\d{1,2})x(\d{1,2})/, // 1x05
/[Ss]eason[.\s](\d{1,2}).*[Ee]p.*(\d{1,2})/, // Season 1 Ep 5
/[Ee]pisode[.\s](\d{1,2})/ // Episode 5 (assumes season 1)
];
let season = null;
let episode = null;
for (const pattern of patterns) {
const match = filename.match(pattern);
if (match) {
if (match[2]) {
season = match[1].padStart(2, '0');
episode = match[2].padStart(2, '0');
} else {
season = '01';
episode = match[1].padStart(2, '0');
}
break;
}
}
if (season && episode) {
// Extract show name (everything before the episode pattern)
const showName = filename.split(/[Ss]\d|Episode|\d+x/)[0]
.replace(/[._]/g, ' ')
.trim()
.replace(/\s+/g, '-');
return `${showName}-S${season}E${episode}`;
}
return filename;Example Results:
The.Show.S01E05.720p.mkv→The-Show-S01E05.mkvThe.Show.1x05.HDTV.mkv→The-Show-S01E05.mkvThe Show Episode 5.mp4→The-Show-S01E05.mp4
7. Intelligent Word Capitalization
// Title case with smart handling of acronyms and prepositions
const smallWords = new Set(['a', 'an', 'the', 'and', 'but', 'or', 'for', 'nor', 'on', 'at', 'to', 'from', 'by', 'in', 'of']);
function toTitleCase(str) {
return str
.toLowerCase()
.split(/[-_\s]+/)
.map((word, index) => {
// Always capitalize first and last word
if (index === 0 || index === str.split(/[-_\s]+/).length - 1) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
// Keep small words lowercase unless they're acronyms
if (smallWords.has(word) && word.length > 1) {
return word;
}
// Check if it's an acronym (all caps and short)
if (word.toUpperCase() === word && word.length <= 4) {
return word; // Keep acronyms as-is
}
return word.charAt(0).toUpperCase() + word.slice(1);
})
.join('-');
}
return toTitleCase(filename);Example Results:
the-quick-brown-fox.txt→The-Quick-Brown-Fox.txtguide-to-using-html-and-css.pdf→Guide-to-Using-HTML-and-CSS.pdfreport-for-the-CEO.docx→Report-for-the-CEO.docx
8. Removing Duplicates and Cleaning
// Remove common download artifacts and clean up
function cleanFilename(name) {
// Remove copy numbers like (1), (2), - Copy, etc.
name = name.replace(/\s*[-_]?\s*\(?\d+\)?$/g, '');
name = name.replace(/\s*[-_]?\s*Copy(\s*\d+)?$/gi, '');
// Remove version markers
name = name.replace(/[-_]v?\d+(\.\d+)*$/gi, '');
// Remove common suffixes
name = name.replace(/[-_](final|draft|old|new|temp|backup)\d*$/gi, '');
// Replace multiple separators with single hyphen
name = name.replace(/[-_\s]+/g, '-');
// Remove leading/trailing separators
name = name.replace(/^[-_]+|[-_]+$/g, '');
return name;
}
return cleanFilename(filename);Example Results:
Document (1).pdf→Document.pdfFile - Copy (3).txt→File.txtReport_v2.3_final.docx→Report.docxData___Old__Backup.xlsx→Data.xlsx
Complex Real-World Scenarios
9. Photo Organization by Date with Counter
// Organize photos by date with sequential numbering
const date = new Date(lastModified);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
// Create counter based on index for photos on same date
const photoNumber = String(index + 1).padStart(4, '0');
// Extract location or event from filename if present
const eventMatch = filename.match(/(?:^|[-_])([A-Za-z]+)(?:[-_]|$)/);
const event = eventMatch ? eventMatch[1].toLowerCase() : 'photo';
return `${year}-${month}-${day}-${event}-${photoNumber}`;Example Results:
- Photo 1 from Oct 5, 2024:
2024-10-05-vacation-0001.jpg - Photo 2 from Oct 5, 2024:
2024-10-05-vacation-0002.jpg
10. Project File Organization
// Organize project files with type prefix and date suffix
const typeMap = {
// Documents
'pdf': 'DOC',
'doc': 'DOC',
'docx': 'DOC',
'txt': 'DOC',
// Spreadsheets
'xlsx': 'DATA',
'xls': 'DATA',
'csv': 'DATA',
// Presentations
'pptx': 'PRES',
'ppt': 'PRES',
// Images
'jpg': 'IMG',
'jpeg': 'IMG',
'png': 'IMG',
'gif': 'IMG',
// Code
'js': 'CODE',
'ts': 'CODE',
'py': 'CODE',
'java': 'CODE',
};
const fileType = typeMap[extension.toLowerCase()] || 'FILE';
// Get modification date
const date = new Date(lastModified);
const dateStr = `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}`;
// Clean and standardize filename
const cleanName = filename
.replace(/[-_]/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.replace(/\s/g, '-')
.toLowerCase();
return `${fileType}-${cleanName}-${dateStr}`;Example Results:
meeting notes.docx→DOC-meeting-notes-20241005.docxbudget 2024.xlsx→DATA-budget-2024-20241005.xlsxcompany logo.png→IMG-company-logo-20241005.png
11. Bulk Download Cleanup with Intelligence
// Clean up downloaded files intelligently
function intelligentCleanup(name) {
// Remove download indicators
name = name.replace(/\s*[-_]?\s*download(?:ed)?/gi, '');
// Remove site names (common download patterns)
name = name.replace(/[-_](from|via)[-_][a-z]+[-_]?/gi, '');
// Remove file IDs and hashes
name = name.replace(/[-_][a-f0-9]{8,}/gi, '');
// Remove brackets and parentheses with contents
name = name.replace(/\s*[\[(][^\])]*[\])]\s*/g, ' ');
// Remove common prefixes
name = name.replace(/^(download|file|document|image)[-_\s]+/gi, '');
// Convert underscores and multiple spaces to single hyphen
name = name.replace(/[_\s]+/g, '-');
name = name.replace(/-{2,}/g, '-');
// Capitalize first letter of each word
name = name.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
).join('-');
// Remove leading/trailing separators
name = name.replace(/^[-\s]+|[-\s]+$/g, '');
return name;
}
return intelligentCleanup(filename);Example Results:
download-important-document-a8f3bc12.pdf→Important-Document.pdffile_from_website_[2024].txt→Website-2024.txtimage__download__vacation__(1).jpg→Vacation-1.jpg
12. Academic Paper Organization
// Organize academic papers with author-year-title format
function extractMetadata(name) {
// Try to extract author(s), year, and title
// Pattern 1: "Author (Year) Title"
let match = name.match(/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s*\((\d{4})\)\s*(.+)$/);
if (match) {
return { author: match[1], year: match[2], title: match[3] };
}
// Pattern 2: "Author - Year - Title"
match = name.match(/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s*[-–]\s*(\d{4})\s*[-–]\s*(.+)$/);
if (match) {
return { author: match[1], year: match[2], title: match[3] };
}
// Pattern 3: Extract year anywhere and assume rest is title
match = name.match(/(\d{4})/);
if (match) {
const year = match[1];
const title = name.replace(/\d{4}[-_\s]*/, '').trim();
return { author: 'Unknown', year, title };
}
return null;
}
const metadata = extractMetadata(filename);
if (metadata) {
// Format: Author_Year_Title-Cleaned
const author = metadata.author.replace(/\s+/g, '_');
const title = metadata.title
.replace(/[^a-zA-Z0-9\s]/g, '')
.replace(/\s+/g, '_')
.slice(0, 50); // Limit title length
return `${author}_${metadata.year}_${title}`;
}
return filename;Example Results:
Smith (2023) Machine Learning Paper.pdf→Smith_2023_Machine_Learning_Paper.pdfJohnson - 2022 - Data Analysis.pdf→Johnson_2022_Data_Analysis.pdfResearchPaper2024Results.pdf→Unknown_2024_ResearchPaperResults.pdf
Utility Functions Library
String Manipulation Helpers
// Collection of reusable string utility functions
// Convert to slug format
function slugify(str) {
return str
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_]+/g, '-')
.replace(/^-+|-+$/g, '');
}
// Convert to camelCase
function toCamelCase(str) {
return str
.replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase())
.replace(/^(.)/, (_, c) => c.toLowerCase());
}
// Convert to PascalCase
function toPascalCase(str) {
return str
.replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase())
.replace(/^(.)/, (_, c) => c.toUpperCase());
}
// Convert to snake_case
function toSnakeCase(str) {
return str
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/[-\s]+/g, '_')
.replace(/^_+|_+$/g, '');
}
// Remove diacritics (accented characters)
function removeDiacritics(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
// Truncate to length with ellipsis
function truncate(str, length) {
return str.length > length ? str.slice(0, length - 3) + '...' : str;
}
// Example usage:
return slugify(filename); // my-filename
// return toCamelCase(filename); // myFilename
// return toPascalCase(filename); // MyFilename
// return toSnakeCase(filename); // my_filenameDate Formatting Helpers
// Date formatting utilities
function formatDate(timestamp, format = 'YYYY-MM-DD') {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const formats = {
'YYYY-MM-DD': `${year}-${month}-${day}`,
'YYYYMMDD': `${year}${month}${day}`,
'DD-MM-YYYY': `${day}-${month}-${year}`,
'MM-DD-YYYY': `${month}-${day}-${year}`,
'YYYY-MM-DD-HH-MM': `${year}-${month}-${day}-${hour}-${minute}`,
};
return formats[format] || formats['YYYY-MM-DD'];
}
// Get relative date string
function getRelativeDate(timestamp) {
const now = Date.now();
const diffDays = Math.floor((now - timestamp) / (1000 * 60 * 60 * 24));
if (diffDays === 0) return 'today';
if (diffDays === 1) return 'yesterday';
if (diffDays < 7) return `${diffDays}days-ago`;
if (diffDays < 30) return `${Math.floor(diffDays / 7)}weeks-ago`;
if (diffDays < 365) return `${Math.floor(diffDays / 30)}months-ago`;
return `${Math.floor(diffDays / 365)}years-ago`;
}
// Example usage:
return `${filename}-${formatDate(lastModified, 'YYYYMMDD')}`;
// return `${filename}-${getRelativeDate(lastModified)}`;Best Practices
1. Always Return a Valid Filename
// Bad - might return undefined
let newName;
if (someCondition) {
newName = 'something';
}
return newName; // Could be undefined!
// Good - always returns something
let newName = filename; // Default to original
if (someCondition) {
newName = 'something';
}
return newName;2. Handle Edge Cases
// Check for empty or invalid inputs
if (!filename || filename.trim() === '') {
return `unnamed-${index + 1}`;
}
// Handle very long filenames
const maxLength = 200;
if (newFilename.length > maxLength) {
newFilename = newFilename.slice(0, maxLength);
}3. Avoid Invalid Characters
// Remove or replace characters that are invalid in filenames
function sanitizeFilename(name) {
// Invalid characters: \ / : * ? " < > |
return name.replace(/[\\/:*?"<>|]/g, '-');
}
return sanitizeFilename(newFilename);4. Be Consistent with Separators
// Choose one separator and stick to it
const SEPARATOR = '-'; // or '_'
// Normalize all separators
const normalized = filename
.replace(/[-_\s]+/g, SEPARATOR)
.replace(/^[-_]+|[-_]+$/g, '');
return normalized;5. Test with Different File Types
// Make your script work with all file types
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(extension.toLowerCase());
const isDocument = ['pdf', 'doc', 'docx', 'txt'].includes(extension.toLowerCase());
const isVideo = ['mp4', 'avi', 'mkv', 'mov'].includes(extension.toLowerCase());
if (isImage) {
// Image-specific logic
} else if (isDocument) {
// Document-specific logic
} else if (isVideo) {
// Video-specific logic
} else {
// Generic logic
}Debugging Tips
Console Logging (if supported)
// Use console.log to debug (check if your tool supports it)
console.log('Filename:', filename);
console.log('Extension:', extension);
console.log('Size:', size);
// Your logic here
const result = someTransformation(filename);
console.log('Result:', result);
return result;Incremental Development
// Build complex transformations step by step
let result = filename;
// Step 1: Clean
result = result.replace(/[-_]+/g, '-');
console.log('After cleaning:', result);
// Step 2: Add date
const date = formatDate(lastModified);
result = `${date}-${result}`;
console.log('After date:', result);
// Step 3: Add index
result = `${result}-${index + 1}`;
console.log('After index:', result);
return result;Conclusion
JavaScript scripting for file renaming transforms a simple utility into a powerful automation tool. Whether you’re dealing with complex naming patterns, need conditional logic, or want to implement custom business rules, JavaScript provides the flexibility to handle any scenario.
Key takeaways:
- Start Simple: Begin with basic transformations and add complexity gradually
- Use Functions: Break complex logic into reusable functions
- Handle Errors: Always account for edge cases and invalid inputs
- Test Thoroughly: Preview results before applying to ensure correctness
- Build a Library: Save useful scripts and functions for future use
With Nomio’s Scripting Mode, you have the full power of JavaScript at your fingertips—no installation, no setup, just open your browser and start scripting.
Ready to automate your file renaming? Try Nomio today and experience the power of JavaScript-based file renaming without any installation required.
Bonus: Script Template
/**
* File Renaming Script Template
*
* Available variables:
* - filename: current filename without extension
* - extension: file extension without dot
* - originalName: full original filename
* - index: file index (0-based)
* - total: total number of files
* - size: file size in bytes
* - lastModified: last modified timestamp
*/
// ===== CONFIGURATION =====
const PREFIX = '';
const SUFFIX = '';
const SEPARATOR = '-';
// ===== UTILITY FUNCTIONS =====
function slugify(str) {
return str.toLowerCase().replace(/[^\w\s-]/g, '').replace(/[\s_]+/g, '-');
}
// ===== MAIN LOGIC =====
let newName = filename;
// Your transformation logic here
// ...
// Clean up and return
newName = newName.replace(/^[-_]+|[-_]+$/g, '');
return newName;Copy this template into Nomio’s Scripting Mode and customize it for your specific needs!