Node.js bindings for macOS Spotlight's
mdfindcommand
The mdfind utility provides core search functionality for Spotlight, allowing you to find files based on their content and metadata. Features include:
- Full text and metadata search
- Live updates for file changes
- Directory-scoped searches
- Filename-only searches
- Raw query and interpreted query support
import { mdfind } from 'mdfind-node'
// Basic text search
const results = await mdfind('skateboard')
// Search in specific directory
const docs = await mdfind('report', {
onlyIn: '~/Documents'
})
// Search by filename
const configs = await mdfind('', {
name: '*.json',
onlyIn: process.cwd()
})| Option | Type | Default | Description |
|---|---|---|---|
onlyIn |
string |
- | Limit search to specific directory |
name |
string |
- | Search by filename pattern |
live |
boolean |
false |
Enable real-time updates |
count |
boolean |
false |
Return only count of matches |
attr |
string |
- | Return specific metadata attribute |
smartFolder |
string |
- | Use saved search |
nullSeparator |
boolean |
false |
Use null character as separator |
maxBuffer |
number |
10MB |
Maximum buffer size for results |
reprint |
boolean |
false |
Reprint results in live mode |
literal |
boolean |
false |
Disable special query interpretation |
interpret |
boolean |
false |
Enable natural language interpretation |
// Simple text
await mdfind('term')
// Wildcards
await mdfind('*.pdf')
// Boolean operations
await mdfind('term1 && term2')
await mdfind('term1 || term2')
// Grouping
await mdfind('(term1 || term2) && term3')// Exact match
await mdfind('kMDItemAuthor == "John Doe"')
// Contains
await mdfind('kMDItemTextContent == "*search*"')
// Comparison
await mdfind('kMDItemPixelHeight > 1080')
// Date
await mdfind('kMDItemContentCreationDate > $time.today(-30)')Monitor for file changes in real-time:
import { mdfindLive } from 'mdfind-node'
const search = mdfindLive(
'kMDItemContentType == "public.image"',
{
onlyIn: '~/Pictures',
reprint: true
},
{
onResult: paths => {
console.log('Updated matches:', paths)
},
onError: error => {
if (error.stderr.includes('invalid query')) {
console.error('Invalid query syntax')
} else {
console.error('Search error:', error.message)
}
},
onEnd: () => {
console.log('Search ended')
}
}
)
// Stop monitoring when done
search.kill()The utility provides detailed error information through the MdfindError class:
import { mdfind, MdfindError } from 'mdfind-node'
try {
await mdfind('invalid:query')
} catch (error) {
if (error instanceof MdfindError) {
console.error('Search failed:', error.message)
console.error('Command output:', error.stderr)
}
}Here are some frequently used metadata attributes:
// File information
kMDItemDisplayName // File name
kMDItemFSName // File system name
kMDItemFSSize // File size
kMDItemContentType // UTI type
kMDItemKind // Localized type
// Dates
kMDItemContentCreationDate // Creation date
kMDItemContentModificationDate // Modified date
kMDItemLastUsedDate // Last used
// Content
kMDItemTextContent // File content
kMDItemTitle // Document title
kMDItemAuthors // Authors
kMDItemKeywords // Tags/keywords
// Media
kMDItemPixelHeight // Image height
kMDItemPixelWidth // Image width
kMDItemDurationSeconds // Media duration
kMDItemCodecs // Media codecs- Empty queries are not allowed unless using the
-nameoption - Live updates cannot be combined with count option
- Literal and interpret options cannot be used together
- The default buffer size is 10MB
- Home directory paths (~/...) are automatically expanded
// Find PDF files
const pdfs = await mdfind('kMDItemContentType == "com.adobe.pdf"')
// Find by name pattern
const images = await mdfind('', {
name: '*.jpg',
onlyIn: '~/Pictures'
})
// Count matches
const count = await mdfind('kind:image', {
count: true
})// Find high-res images
const images = await mdfind('kMDItemPixelHeight > 1080 && kMDItemPixelWidth > 1920')
// Find recent documents
const docs = await mdfind('kMDItemContentModificationDate > $time.today(-7)')
// Find by author
const authored = await mdfind('kMDItemAuthors == "John Doe"')// Monitor for new images
const search = mdfindLive(
'kind:image',
{
onlyIn: '~/Pictures',
reprint: true
},
{
onResult: paths => {
for (const path of paths) {
console.log('New or modified image:', path)
}
},
onError: error => console.error(error),
onEnd: () => console.log('Monitoring ended')
}
)
// Stop after 5 minutes
setTimeout(() => search.kill(), 5 * 60 * 1000)For incremental consumption without buffering, use mdfindStream:
import { mdfindStream } from 'mdfind-node'
// Process results one at a time as they arrive
const stream = mdfindStream('kind:image', { onlyIn: '~/Pictures' })
for await (const filePath of stream) {
console.error('Found:', filePath)
}
// Collect a limited number of results
const stream2 = mdfindStream('kind:pdf')
const results: string[] = []
for await (const filePath of stream2) {
results.push(filePath)
if (results.length >= 10) {
stream2.stop()
}
}The LiveSearchStream returned by mdfindStream exposes:
stop()— terminate the search processprocess— the underlyingChildProcessfor advanced control
- Query Builder Documentation - Type-safe query construction
- mdls Documentation - Get metadata for files
- mdutil Documentation - Manage Spotlight index
- macOS mdfind Manual
- Spotlight Query Format