| name | obsidian-data-handling |
| description | Implement vault data backup, sync, and recovery strategies.
Use when building backup features, implementing data export,
or handling vault synchronization in your plugin.
Trigger with phrases like "obsidian backup", "obsidian sync",
"obsidian data export", "vault backup strategy".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Obsidian Data Handling
Overview
Implement robust data handling patterns for vault backup, export, sync, and recovery in Obsidian plugins.
Prerequisites
- Understanding of Obsidian file system
- Knowledge of data serialization formats
- Basic understanding of sync patterns
Data Categories
| Data Type | Storage Location | Backup Priority |
|---|
| Notes (md) | Vault root | Critical |
| Attachments | Vault/attachments | Critical |
| Plugin settings | .obsidian/plugins/*/data.json | High |
| Plugin cache | .obsidian/plugins/*/cache/ | Low |
| Vault config | .obsidian/*.json | Medium |
Instructions
Step 1: Data Export Service
import { App, TFile, TFolder, Notice } from 'obsidian';
export interface ExportOptions {
includeAttachments: boolean;
includeMetadata: boolean;
format: 'json' | 'markdown' | 'zip';
folder?: string;
}
export interface ExportedNote {
path: string;
content: string;
metadata?: Record<string, any>;
created: number;
modified: number;
}
export class ExportService {
constructor(private app: App) {}
async exportNotes(options: ExportOptions): Promise<ExportedNote[]> {
const files = this.app..();
filteredFiles = options.
? files.( f..(options.!))
: files;
: [] = [];
( file filteredFiles) {
content = ...(file);
metadata = options.
? ...(file)?.
: ;
exportedNotes.({
: file.,
content,
metadata,
: file..,
: file..,
});
}
exportedNotes;
}
(: ): <> {
notes = .(options);
exportData = {
: ().(),
: ...(),
: notes.,
notes,
};
.(exportData, , );
}
(: , : ): <> {
json = .(options);
blob = ([json], { : });
url = .(blob);
a = .();
a. = url;
a. = filename;
a.();
.(url);
();
}
}
Step 2: Data Import Service
import { App, TFile, Notice } from 'obsidian';
export interface ImportOptions {
overwrite: boolean;
targetFolder?: string;
dryRun: boolean;
}
export interface ImportResult {
created: string[];
updated: string[];
skipped: string[];
errors: { path: string; error: string }[];
}
export class ImportService {
constructor(private app: App) {}
async importFromJson(
jsonContent: string,
options: ImportOptions
): Promise<ImportResult> {
const result: ImportResult = {
created: [],
updated: [],
skipped: [],
errors: [],
};
{
data = .(jsonContent);
notes = data. || [];
( note notes) {
{
targetPath = options.
?
: note.;
existing = ...(targetPath);
(existing ) {
(options.) {
(!options.) {
...(existing, note.);
}
result..(targetPath);
} {
result..(targetPath);
}
} {
(!options.) {
.(targetPath);
...(targetPath, note.);
}
result..(targetPath);
}
} (error) {
result..({
: note.,
: (error ).,
});
}
}
} (error) {
result..({
: ,
: ,
});
}
result;
}
(: ): <> {
folderPath = filePath.(, filePath.());
(!folderPath) ;
folder = ...(folderPath);
(!folder) {
...(folderPath);
}
}
(
: ,
:
): <> {
content = file.();
.(content, options);
}
}
Step 3: Backup Service
import { App, TFile, Notice } from 'obsidian';
export interface BackupConfig {
autoBackup: boolean;
intervalMinutes: number;
maxBackups: number;
backupFolder: string;
includePluginData: boolean;
}
export interface BackupManifest {
id: string;
timestamp: string;
vault: string;
noteCount: number;
size: number;
checksum: string;
}
export class BackupService {
private config: BackupConfig;
private intervalId: number | null = null;
constructor(private app: App, config: BackupConfig) {
this. = config;
}
(): <> {
timestamp = ().().(, );
backupId = ;
backupFolder = ;
...(backupFolder);
files = ...();
totalSize = ;
( file files) {
content = ...(file);
targetPath = ;
.(targetPath);
...(targetPath, content);
totalSize += content.;
}
checksum = .(files);
: = {
: backupId,
: ().(),
: ...(),
: files.,
: totalSize,
checksum,
};
...(
,
.(manifest, , )
);
.();
();
manifest;
}
(: []): <> {
encoder = ();
combinedContent = ;
( file files.( a..(b.))) {
content = ...(file);
combinedContent += ;
}
data = encoder.(combinedContent);
hashBuffer = crypto..(, data);
hashArray = .( (hashBuffer));
hashArray.( b.().(, )).().(, );
}
(): <[]> {
: [] = [];
backupFolder = ...(..);
(!backupFolder) backups;
children = (backupFolder ). || [];
( child children) {
manifestPath = ;
manifestFile = ...(manifestPath);
(manifestFile ) {
content = ...(manifestFile);
backups.(.(content));
}
}
backups.(
(b.).() - (a.).()
);
}
(: , : { : }): <> {
backupFolder = ;
folder = ...(backupFolder);
(!folder) {
();
}
manifestFile = ...(
);
(!(manifestFile )) {
();
}
: = .(
...(manifestFile)
);
(options.) {
();
;
}
importService = (.);
files = ...()
.( f..(backupFolder + ))
.( f. !== );
( file files) {
content = ...(file);
targetPath = file..(backupFolder + , );
.(targetPath);
existing = ...(targetPath);
(existing ) {
...(existing, content);
} {
...(targetPath, content);
}
}
();
}
(): <> {
backups = .();
(backups. > ..) {
toDelete = backups.(..);
( backup toDelete) {
folder = ...(
);
(folder) {
...(folder, );
}
}
}
}
(: ): <> {
folderPath = filePath.(, filePath.());
(!folderPath) ;
folder = ...(folderPath);
(!folder) {
...(folderPath);
}
}
(): {
(!..) ;
. = .(
.(),
.. * *
);
}
(): {
(.) {
.(.);
. = ;
}
}
}
Step 4: Data Validation
import { App, TFile } from 'obsidian';
export interface ValidationResult {
valid: boolean;
errors: ValidationError[];
warnings: ValidationWarning[];
}
interface ValidationError {
file: string;
type: 'frontmatter' | 'link' | 'syntax' | 'encoding';
message: string;
}
interface ValidationWarning {
file: string;
type: string;
message: string;
}
export class ValidationService {
constructor(private app: App) {}
async validateVault(): Promise<ValidationResult> {
const result: ValidationResult = {
valid: true,
errors: [],
warnings: [],
};
files = ...();
( file files) {
.(file, result);
}
result. = result.. === ;
result;
}
(: , : ): <> {
{
content = ...(file);
.(file, content, result);
.(file, result);
(.(content)) {
result..({
: file.,
: ,
: ,
});
}
} (error) {
result..({
: file.,
: ,
: ,
});
}
}
(
: ,
: ,
:
): {
frontmatterMatch = content.();
(frontmatterMatch) {
{
yaml = frontmatterMatch[];
(yaml.()) {
result..({
: file.,
: ,
: ,
});
}
(.(yaml)) {
result..({
: file.,
: ,
: ,
});
}
} (error) {
result..({
: file.,
: ,
: ,
});
}
}
}
(: , : ): {
cache = ...(file);
(!cache?.) ;
( link cache.) {
linkedFile = ...(
link.,
file.
);
(!linkedFile) {
result..({
: file.,
: ,
: ,
});
}
}
}
(: ): {
nullByteCount = (content.() || []).;
nullByteCount > ;
}
}
Step 5: Data Sync Patterns
import { App, TFile } from 'obsidian';
export interface SyncStatus {
lastSync: string | null;
pendingChanges: number;
conflicts: SyncConflict[];
}
export interface SyncConflict {
path: string;
localModified: number;
remoteModified: number;
resolution: 'local' | 'remote' | 'manual' | null;
}
export class SyncService {
private syncHashes = new Map<string, string>();
private pendingChanges = new Set<string>();
constructor(private app: App) {}
async trackChange(file: TFile): Promise<void> {
const hash = .(file);
previousHash = ..(file.);
(previousHash && previousHash !== hash) {
..(file.);
}
..(file., hash);
}
(): <[]> {
: [] = [];
( path .) {
file = ...(path);
(file ) {
changedFiles.(file);
}
}
changedFiles;
}
(: ): {
..(path);
}
(): {
{
: ,
: ..,
: [],
};
}
(: ): <> {
content = ...(file);
encoder = ();
data = encoder.(content);
hashBuffer = crypto..(, data);
hashArray = .( (hashBuffer));
hashArray.( b.().(, )).().(, );
}
}
Output
- Data export with multiple formats
- Import with conflict handling
- Automated backup with retention
- Vault validation
- Change tracking for sync
Error Handling
| Issue | Cause | Solution |
|---|
| Export fails | Large vault | Use streaming export |
| Import conflicts | Existing files | Provide overwrite option |
| Backup corruption | Interrupted write | Verify with checksum |
| Sync conflicts | Concurrent edits | Implement conflict resolution |
Examples
Quick Backup Command
this.addCommand({
id: 'quick-backup',
name: 'Create Quick Backup',
callback: async () => {
const backupService = new BackupService(this.app, {
autoBackup: false,
intervalMinutes: 60,
maxBackups: 5,
backupFolder: '_backups',
includePluginData: true,
});
await backupService.createBackup();
},
});
Resources
Next Steps
For team access patterns, see obsidian-enterprise-rbac.