Custom Links in tiptap
Today I came across a scenario where I wanted to modify the default behavior of some <a> links in tiptap editor.
The requirement was if the <a> link has an href attribute starting with /map
clicking the link should not open the a new tab. Opening a new tab loads the entire app again in another tab and breaks the user’s context.
The Link
extension in tiptap let’s you set target to null
. However, doing that didn’t work for me. I read that browsers do not let editor links open in the same tab. Read more here Default prevented on <a> click?.
So I extended the Link
extension by creating the CustomLink
extension. I took help from this github comment.
Since tiptap is built on top of ProseMirror API, you can use the addProseMirrorPlugins
in your extensions. When a link is clicked,
Identify the correct <a> tag
if (link && link.attributes.getNamedItem("href")?.value.startsWith("/map"))
stop its propagation.
event.preventDefault(); event.stopPropagation();
Do what needs to be done instead.
extensionOptions.showMap(true);
extensionOptions.setMapQuery(link.text || "");
Here is the complete code.
import Link from "@tiptap/extension-link";
import { Plugin, PluginKey } from "@tiptap/pm/state";
import { getAttributes } from "@tiptap/core";
interface CustomLinkOptions {
showMap: (val: boolean) => void;
setMapQuery: (val: string) => void;
}
export const CustomLink = Link.extend<CustomLinkOptions>({
addOptions() {
return {
...this.parent?.(),
openOnClick: false,
showMap: () => {},
setMapQuery: () => {},
};
},
addProseMirrorPlugins() {
const plugins: Plugin[] = this.parent?.() || [];
const extensionOptions = this.options;
const clickHandler = new Plugin({
key: new PluginKey("handleControlClick"),
props: {
handleClick(view, pos, event) {
const attrs = getAttributes(view.state, "link");
const link = (event.target as HTMLElement)?.closest("a");
if (
link &&
link.attributes.getNamedItem("href")?.value.startsWith("/map")
) {
event.preventDefault();
event.stopPropagation();
extensionOptions.showMap(true);
extensionOptions.setMapQuery(link.text || "");
return true;
} else if (link && attrs.href) {
window.open(attrs.href, attrs.target);
return true;
}
return false;
},
},
});
plugins.push(ctrlClickHandler);
return plugins;
},
});
Here is how you can use the extension in Tiptap.
const editor = useEditor(
{
extensions: [
CustomLink.configure({
showMap: () => {
showMap(true);
},
setMapQuery: (val: string) => {
setMapQuery(val);
},
}),
]
}
//..rest of your code
});
Hope this helps.
251 views
Comments ( 2 )
Matthew Bub
9 months ago
Thanks for the post, I'm building a feature that tracks links throughout an app and TipTap was the edge case. This is like the perfect template for getting started. Cheers!
Satyajeet Jadhav
9 months ago
I’m glad you found it useful! Cheers!
Participate in the conversation.
Never miss a post from
Satyajeet Jadhav
Get notified when Satyajeet Jadhav publishes a new post.
Read More
Thinkdeli Release Notes
This document maintains the list of new features and bug fixes as they happen.

Mastering Internal Linking: Lessons to Boost Your SEO and User Experience
Internal linking is one of the most underrated yet powerful tools in the SEO arsenal. It’s not just about connecting pages, it’s about guiding your audience, boosting your website’s authority and signaling search engines about the structure of your content. Done right, internal l...

Issues
language pills not shifting to left side while making new entry - it cuts off the entered text and makes it hard to understand what we are writing currently.

Formatting on thinkdeli
You can use single ticks around a word to mark it as code too. For example

Is a Sitemap.xml Really Necessary for Your Website?
A sitemap.xml is essentially a roadmap for search engines like Google and Bing. It’s a file that lists all the pages on your website that you want search engines to crawl and index. Think of it as a \"menu\" that helps search engines understand your website structure.

Semantic Search, aka Magic
The related notes feature searches all your notes to find the ones that are closest in meaning to your current note.Searching notes to find text similar in meaning to your query is called semantic search. We are trying to build a semantic search engine.
