A Node.js library for parsing iMessage chat.db database files, specifically handling the attributedBody field that contains NSAttributedString data in Apple's proprietary typedstream format.
- Parse iMessage
attributedBodyfields containing formatted text - Extract plain text from NSAttributedString binary data
- Handle multi-line messages with proper formatting preservation
- Support for numbered lists and sub-items
- Fallback text extraction for corrupted or unknown formats
- Direct database access with search capabilities
- Build deep links to iMessage conversations and messages
npm install imessage-parser
# Or install globally for CLI access
npm install -g imessage-parserThe package includes a comprehensive CLI for working with iMessage data:
# List all chats
imessage-parser list-chats
# Extract messages from a specific chat
imessage-parser extract "Family Chat" -o family-messages.md -f markdown
# Search for messages across all chats
imessage-parser search "vacation plans" --limit 20
# Parse a specific message by ROWID
imessage-parser parse-message 302041 --show-readable
# Show statistics
imessage-parser stats -c "Family Chat"
# Get help
imessage-parser --help- list-chats - List all available chats with their IDs
- extract - Extract messages from a specific chat
- Supports text, JSON, and Markdown output formats
- Can filter by date range
- Optionally includes formatting information
- search - Search messages across all chats
- Supports case-sensitive and regex search
- Highlights search terms in results
- parse-message - Parse and analyze a specific message
- Shows hex dump of attributedBody
- Displays all readable text segments
- Extracts formatting attributes
- stats - Display statistics about messages
- Overall database stats or per-chat statistics
- Message counts, date ranges, and formatting info
const { parseAttributedBody } = require('imessage-parser');
// Parse a Buffer containing attributedBody data
const buffer = /* Buffer from iMessage database */;
const result = parseAttributedBody(buffer);
console.log(result.text); // Extracted message textconst { IMessageDatabase } = require('imessage-parser');
async function readMessages() {
// Connect to the iMessage database
const db = new IMessageDatabase(); // Uses default path: ~/Library/Messages/chat.db
try {
// Get all chats
const chats = await db.getChats();
console.log(`Found ${chats.length} chats`);
// Get messages from a specific chat
const messages = await db.getMessagesFromChat(chats[0].ROWID, 50);
for (const message of messages) {
const parsed = db.parseMessage(message);
console.log(`${message.is_from_me ? 'Me' : 'Them'}: ${parsed.text}`);
}
// Search for messages containing specific text
const results = await db.searchMessages('currency');
console.log(`Found ${results.length} messages containing "currency"`);
} finally {
await db.close();
}
}const { AttributedStringParser } = require('imessage-parser');
const parser = new AttributedStringParser({
preserveFormatting: true, // Preserve line breaks and formatting
includeMetadata: true, // Include attributes and raw data
cleanOutput: true, // Clean up whitespace and artifacts
encoding: 'utf8' // Text encoding
});
const result = parser.parse(attributedBodyBuffer);
console.log(result.text); // Parsed text
console.log(result.attributes); // Message attributes (if any)
console.log(result.rawData); // Original bufferconst { IMessageDatabase } = require('imessage-parser');
async function extractFormattedMessages() {
const db = new IMessageDatabase();
try {
// Get messages from the last week
const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);
const endDate = new Date();
const messages = await db.getAttributedMessagesInRange(
startDate,
endDate,
'Family Chat' // Optional: filter by chat name
);
for (const message of messages) {
const parsed = db.parseMessage(message);
if (parsed.text.includes('?')) {
console.log('\nQuestion found:');
console.log(parsed.text);
console.log('---');
}
}
} finally {
await db.close();
}
}Convenience function to parse a single attributedBody buffer.
buffer- Buffer containing NSAttributedString dataoptions- Optional parser options- Returns:
ParsedMessageobject with extracted text
Main class for accessing the iMessage database.
constructor(dbPath?, parserOptions?)- Create new database instancegetChats()- Get all chat conversationsgetMessagesFromChat(chatId, limit?, offset?)- Get messages from a specific chatparseMessage(message)- Parse a message row to extract textsearchMessages(searchTerm, limit?)- Search messages by contentgetAttributedMessagesInRange(startDate, endDate, chatName?)- Get messages with attributedBody in date rangeclose()- Close database connection
Core parser for NSAttributedString format.
constructor(options?)- Create new parser instanceparse(buffer)- Parse attributedBody buffer
preserveFormatting- Preserve line breaks and formatting (default: true)includeMetadata- Include attributes and raw data in result (default: false)cleanOutput- Clean up whitespace and artifacts (default: true)encoding- Text encoding (default: 'utf8')
The parser can extract various formatting attributes from messages:
- Text Styles: Bold, italic, underline, strikethrough
- Links: URLs with their display text and position
- Mentions: @mentions with phone numbers or emails
- Data Detectors: Dates, times, addresses, phone numbers, flights, measurements
- Fonts: Font family, size, weight, and style
- Colors: Text and background colors with RGB values
- Attachments: Attachment indicators and metadata
const { IMessageDatabase } = require('imessage-parser');
const db = new IMessageDatabase(undefined, { includeMetadata: true });
const messages = await db.getMessagesFromChat(chatId);
messages.forEach(msg => {
const parsed = db.parseMessage(msg);
if (parsed.attributes?.links) {
parsed.attributes.links.forEach(link => {
console.log(`Link: ${link.url} at position ${link.location}`);
});
}
if (parsed.attributes?.bold) {
parsed.attributes.bold.forEach(range => {
const boldText = parsed.text.substring(range.location, range.location + range.length);
console.log(`Bold text: "${boldText}"`);
});
}
if (parsed.attributes?.dataDetectors) {
parsed.attributes.dataDetectors.forEach(detector => {
console.log(`Detected ${detector.type}: ${detector.value}`);
});
}
});iMessage stores formatted messages in the attributedBody field using Apple's NSAttributedString format, serialized with NSArchiver (typedstream format). This library:
- Identifies NSString objects within the binary data
- Extracts text content following the typedstream format specification
- Parses NSDictionary structures containing formatting attributes
- Reconstructs the original message with proper formatting
- Falls back to readable text extraction for unknown formats
The typedstream format for NSString typically follows this pattern:
| "NSString" | preamble (5 bytes) | length | content |
Where:
- Preamble is usually
0x01 0x94 0x84 0x01 0x2b - Length is either 1 byte or 3 bytes (if first byte is
0x81) - Content is UTF-8 encoded text
Formatting attributes are stored in NSDictionary structures with range information indicating where the formatting applies in the text.
- Node.js 14 or higher
- macOS (for accessing iMessage database)
- Read access to
~/Library/Messages/chat.db
The library can automatically generate deep links to open specific messages or conversations in the Messages app.
const { IMessageDatabase } = require('imessage-parser');
const db = new IMessageDatabase();
// Parse message - link is always included
const messages = await db.getMessagesFromChat(chatId, 1);
const parsed = db.parseMessage(messages[0]);
console.log(parsed);
// {
// text: "Hello world",
// link: "messages://open?guid=12345-67890-ABCDEF"
// }
// Get messages with full chat context for better links
const messagesWithChat = await db.getMessagesWithChatInfo(chatId);
const parsedWithChat = db.parseMessageWithChat(messagesWithChat[0]);
console.log(parsedWithChat.link);
// Automatically selects best link type:
// - "messages://open?guid=<message-guid>" (specific message)
// - "sms://+15551234567" (conversation fallback)
// - "messages://open?guid=<chat-guid>" (group chat fallback)For more control over link generation:
const { buildChatLinks, buildMessageLink, buildComposeLink } = require('imessage-parser');
// Get all possible links for a chat
const chatLinks = buildChatLinks(chat);
// Build link for specific message
const messageLink = buildMessageLink(message);
// Build compose link with pre-filled text
const composeLink = buildComposeLink('+15551234567', 'Hello!');sms://- Opens conversations (universally supported)imessage://- Alternative for opening conversationsmessages://- Opens specific messages (requires special entitlements in some contexts)
📖 See the Deep Linking Guide for comprehensive documentation on URL schemes, special entitlements, platform compatibility, and troubleshooting.
When using this library, please be mindful of privacy:
- The iMessage database contains personal conversations
- Always obtain consent before accessing or processing someone else's messages
- Be careful not to commit message data to version control
- Consider anonymizing data when sharing examples or debugging
- Only works on macOS with local iMessage database
- Cannot decrypt messages if database is encrypted
- Some complex formatting may not be fully preserved
- Attributes (bold, italic, links) extraction is not yet implemented
- Deep links to specific messages may not work in all contexts
Contributions are welcome! Please feel free to submit a Pull Request.
MIT