(ReOpen) Automating the Renaming of Notes with Unaccepted Characters in Obsidian

Hello,

I have transferred my notes from Joplin to Obsidian. However, my note titles contain characters like #, ?, and : that Obsidian does not accept. Therefore, the titles are incomplete. But the full titles of my notes are available in the ‘Title’ field. So, I have to manually adjust the titles using an application called ‘Smart Rename’. This means I am copying the file name from the ‘Title’ field without unwanted characters and pasting it into the note name automatically. Considering that I have a large number of notes, is it possible to automate this process for all notes?

I don’t have any experience in coding. I would appreciate it if programmer friends could help with the script. @mnaoumov

Although I’ve been manually editing the titles of my notes for a few months, this topic will be useful and important for users like me who have transitioned from note-taking apps like Joplin to Obsidian.



DON’T FORGET TO BACKUP YOUR VAULT BEFORE RUNNING ANY CODE

Take the following js code and insert it in Obsidian anywhere that can run js code, e.g. Ctrl + Shift + I, dataviewjs etc

The code does the following

  1. Loops over all md files in your vault
  2. Checks if the corresponding file has title in frontmatter
  3. Replaces all unsafe characters to underscore (_)
  4. Ensures the result file name and path don’t exceed certain length limits:
    • 259 characters in total on Windows
    • 255 bytes in name, non-English characters can take more than 1 byte
  5. Trims trailing spaces ( ) and periods (.) as they are not valid on Windows
  6. Renames the file into the safe title
  7. Outputs the progress and all errors, if any

Feel free to ask for more features if needed

async function renameAllNotesToTheirTitles() {
  // https://superuser.com/questions/1620259/the-maximum-length-of-the-path-of-a-file-259-or-258-characters
  const MAX_FILE_PATH_LENGTH = 259;

  // https://serverfault.com/questions/9546/filename-length-limits-on-linux
  const MAX_NAME_BYTES_LENGTH = 255;

  const INVALID_CHARACTERS_REG_EXP = /[#\^\[\]|*\\\/<>:?]/g;
  const REPLACEMENT_CHARACTER = '_';

  const basePath = app.vault.adapter.basePath;

  let i = 0;
  const notes = app.vault.getMarkdownFiles().sort((a, b) => a.path.localeCompare(b.path));
  for (const note of notes) {
    i++;
    console.log(`Processing ${i} / ${notes.length} - ${note.path}`);

    try {
      let title = app.metadataCache.getFileCache(note)?.frontmatter?.['title'];

      if (!title) {
        console.warn(`No title found for ${note.path}`);
        title = note.basename;
      }

      title = String(title);

      const safeTitle = title.replace(INVALID_CHARACTERS_REG_EXP, REPLACEMENT_CHARACTER);

      const dir = note.parent?.getParentPrefix() ?? '';
      const fullDir = `${basePath}/${dir}`;
      const fullPath = `${fullDir}${safeTitle}.md`;

      let trimmedSafeTitle = safeTitle;

      const trimLength = Math.max(fullPath.length - MAX_FILE_PATH_LENGTH, bytesLength(title) - MAX_NAME_BYTES_LENGTH + '.md'.length);

      if (trimLength >= safeTitle.length) {
        console.error(`Cannot set safe title for ${fullPath} as the path is too long`);
      } else {
        trimmedSafeTitle = safeTitle.slice(0, safeTitle.length - trimLength);
      }

      trimmedSafeTitle = trimmedSafeTitle.replace(/[ .]+$/, REPLACEMENT_CHARACTER);

      const newPath = `${dir}${trimmedSafeTitle}.md`;
      if (note.path === newPath) {
        console.log('No need to rename');
        continue;
      }
      console.log(`Renaming ${note.path} to ${newPath}`);
      await app.fileManager.renameFile(note, newPath);
    } catch (e) { 
      console.error(`Error processing ${note.path}`, e);
    }
  }
}

function bytesLength(str) {
  return new Blob([str]).size;
}

await renameAllNotesToTheirTitles();