Save regex search and replace patterns that can be executed as commands or in Commander macros.
Things I have tried
I’ve used Regex Pipeline, but it hasn’t been updated in almost three years, and recently escaped characters in replace strings stopped rendering correctly.
Regex Find/Replace doesn’t save commands and hasn’t been updated in almost four years.
I’ve seen references to using VS Code to do replacements across a vault. I can do this with TextMate on MacOS, but I can’t execute saved replacements as commands.
Is there a plugin or other method to execute saved regex replacements as Obsidian commands?
The plugin lives in your sidebar, can save and load patterns and can replace on the whole file or selection only.
No need for commands as you pick your replacement rule.
I think I updated the post with the latest bug fix. But I think I may have since added to the plugin.
Save as ‘main.js’:
{
"id": "regex-replacer",
"name": "Regex Replacer",
"version": "2.0.0",
"minAppVersion": "0.15.0",
"description": "Regex-Replacer with Sidebar support, originally by Frank Wisniewski",
"author": "various",
"isDesktopOnly": false
}
Put these files in an folder, .e.g. ‘regex-replacer’ and put it in your plugins folder. Start Obsidian and enable the plugin and then run the ‘Open Regex Replace Sidebar’ command. From now on it will be in your sidebar. No more commands to run, only use the GUI.
If you click on a match, you’ll be taken to that line in the active file.
You can change settings in script if your PC is not weak and can use RAM to display any amounts, but if regex overmatches, it’s good to keep it low, but of course preview is important to see if you match at all or not:
Apply Patterns lets you save patterns (or chains of patterns), but I believe not as commands (you call one of the commands for the plugin and then choose a pattern).
I seem to remember you need to embed them in Commander Macros…?
My plugin (version of the original shared in the thread linked above) shows live preview of prospective changes with highlights. So you only shoot when ready.
Using it elevated out into Hover Editor window instance:
@Sunnaq445—Thanks for the tip! I needed to rename the plugin to Regex-Replace-js because it conflicted with Regex Replace—I see you renamed it to Regex Replacer. I haven’t tried running any of my patterns yet, but it looks promising.
@CawlinTeffid—I’ll check out Apply Patterns as well. I already use Commander heavily for both menus and macros, so that’s not a problem.
Edit: I got Apply Patterns to run a rule, but I don’t yet see how to run a rule without going through the modal—the documentation is a bit confusing, as are all the plugin options. However, I’m a professional geek, and I’ll figure it out.
For context, my most common application is my publish workflow for micro.blog, which I’ve automated with Commander macros. I use Regex Pipeline to strip out comments and highlights, and the title (which micro.blog inserts in its own template), run micro.publish, then revert the changes made by Regex Pipeline. One handy effect of making all my edits with one call to the Regex Pipeline plugin is that all I need to do to reverse them is a single undo command. Sadly, that plugin hasn’t been updated in three years and is now broken.
Good eyes, yes, there is another plugin with a similar name.
Regex replacements within a file can be undone, which is good. You can mangle your lines and wreak havoc with regex.
When constructing one, you can use AI help and the regex101.com site. Just set the UI to Subsitution below and choose the Js flavour. A simple case to check what you are doing is what you expect:
Try to add more lines to the Test String side with dealing with more elaborate regexes. Try to learn about problem points in regex (like how you can use ? to limit scope; it’s important to know how to defend against losing your data). AI can help but sometimes they act wobbly around regex if you don’t specify exactly what is your ‘Before’ state and ‘After’ requirement.
I agree that adding new patterns and running them with Apply Patterns is a bit wonky. Its saving grace is that you can chain multiple patterns and commands and then run them as Command Palette commands if you use the AP commands in Commander macros (a macro would just be a parent item or a jacket and wouldn’t serve any other purpose), so you can bind these to key shortcuts. But when you have 10-12, you’ll not remember them easily anymore.
My plugin cannot be used to chain multiple commands and I don’t plan to support it.
Now that we are talking about my plugin, please re-copy and re-add to main.js file as I made changes to the script above: now the loaded preset’s name is shown and the last used one persists on to the next session.
To be honest, I created my own custom replacer scripts for cases when I make dozens of changes to an important sheet of text and once the script is done, you can easily add new replacements that way. Quicker for me than using the Apply Patterns plugin’s settings panel, but I’d rather not go into that now.
I have used that and RegExr.com. I also have Expressions via a Setapp subscription. I have enough programming background that regex isn’t too scary—I just need to consult the documentation a lot.
Which is why I always test using one of the tools above.
Ya think?
That’s basically what I do with Regex Pipeline. The interface is a bit clunky, but the author replied to my issue post on GitHub to say that \n in the replacement string rendering as a literal was the expected behavior. It’s unlike any other regex I’ve seen, but it does do what’s in the documentation. What I like about it is that once you’ve set it up, it does what it needs to do and stays out of the way.
Actually, the best part of the author responding is that I know now that they haven’t abandoned the plugin, which is why I made this post in the first place. I likely will continue to use that plugin for saved replacements, but the others you both pointed out are improvements on what I’d been using for one-off changes. That plugin has no preview (very dangerous) and hasn’t been updated in four years.
Note: I found that \\ rendering as one or two backslashes depends on the regex flavor you’re using. That’s just run-of-the-mill lack o’ standards.
If I were more experienced with JS, I could do that. I’ve learned enough to make some handy Templater scripts by modifying templates, but I can’t write scripts from scratch. In the 80s I thought I would become a programmer, but that’s not where life took me, and I don’t have the energy to learn now. I can do most of what I want and even help my daughter with her coding homework, so I’m good.
Fair enough. Yeah, one of the problem points with these plugins that you need to submit FR’s to devs for \n to be accepted for replacement.
Well, I also learn along the way. It takes some 20-30 versions on average to finally get what you want and maybe come back to it every 2-3 months to fix something that you didn’t know it was not working properly.
The big advantage of using scripts is that for certain tasks you need to take a subset of your lines (e.g. excluding YAML, your mermaid/plantuml diagrams, etc) and replace on them conditionally. You cannot do this or very easily with any of the plugins.
A very minimal (stripped-down version of my) script to be used with Codescript Toolkit is like this:
import * as obsidian from 'obsidian';
const processMarkdown = async (app: obsidian.App): Promise<void> => {
const currentFile = app.workspace.getActiveFile();
if (!currentFile) return;
let fileContent = await app.vault.read(currentFile);
// Define your replacements here
const replacements = [
// Placeholder1
{
from: /Placeholder1/g,
to: 'Placeholder1'
},
// Placeholder2
{
from: /Placeholder2/g,
to: 'Placeholder2'
}
];
// Apply all replacements
for (const {from, to} of replacements) {
// Convert string or regex to RegExp if needed
const regex = from instanceof RegExp ? from : new RegExp(from);
// Test if there are any matches before replacing
if (regex.test(fileContent)) {
console.log(`Found matches for pattern: ${regex}`);
// Reset regex lastIndex
regex.lastIndex = 0;
// Apply replacement
fileContent = fileContent.replace(regex, to);
}
}
await app.vault.modify(currentFile, fileContent);
};
export class MarkdownProcessorPlugin extends obsidian.Plugin {
async onload() {
this.addCommand({
id: 'process-markdown',
name: 'Process Markdown',
callback: async () => await processMarkdown(this.app)
});
}
}
export async function invoke(app: obsidian.App): Promise<void> {
return processMarkdown(app);
}
Save it with a telling name, like ‘Zotero-Clean-Up-Script.ts’ or something (has to be .ts at the end) and change class, id and name of script (AI can be used, it’s 2025). Obsviously you need to add proper replacements, with the proper flags (g, gm, gi, whatever) and if needed you can have an AI bot add methods if some more complex work with function replacements is to be done (e.g. converting vtt/srt to markdown, whatever, based on YAML property key values, etc.). You can even add methods where an AI model kicks in because it is computionally impossible to use a regular expression and a natural language method would probably work better.
Then you take a sample like this and you do your next script with different replacements.
Even very non-tech users can add a new rule with
{
from: /Placeholder3/g,
to: 'Placeholder3'
}
(on the last item in the list, we add no commas on the closing brace).
So functions like this can be added easily:
// Smart quote cleanup - only fix quotes that aren't already part of code blocks
{
from: /"([^"]+)"/g,
to: (match, p1) => {
// Skip if inside code (contains backticks)
if (p1.includes('`')) {
return match;
}
return `"${p1}"`;
}
},
// Capitalize sentences after periods (but skip URLs and abbreviations)
{
from: /(\.\s+)([a-z])/g,
to: (match, p1, p2) => {
// Don't capitalize if it looks like a file extension or URL
if (match.includes('.com') || match.includes('.org')) {
return match;
}
return p1 + p2.toUpperCase();
}
},
// Convert timestamps to clickable links (conditional based on video ID presence)
{
from: /\[(\d{1,2}:\d{2})\]/g,
to: (match, timestamp) => {
const videoId = 'YOUR_VIDEO_ID'; // Could be extracted from frontmatter
if (videoId) {
return `[${timestamp}](https://youtube.com/watch?v=${videoId})`;
}
return match; // Keep original if no video ID
}
}
If you like, you can unmark my other post and mark this or just leave you own post as comment and mark that as solution as there are multiple solutions. Cheers
My own personal rule is, the computer can never win, and I’ll bang away at a problem until I figure out how to do what I want. I was able to make Regex Pipeline insert line breaks by including a literal line break within the replacement string—that’s actually in the plugin documentation. It’s wonky as heck, but it works.
Sometime when I have time on my hands (retirement, here I come), I’ll learn more JS.