@docs.plus/extension-hyperlink

中文文档
2.3.0发布于 2026/11/10MITTypeScriptESM + CJS近 30 天下载 2285

Tiptap extension for hyperlinks with built-in popovers, auto-linking, and 50+ URL scheme support

原始使用文档
文档目录16 节
  1. Why use it
  2. Install
  3. Quickstart
  4. Caveats
  5. Common tasks
  6. Set a link from your own button
  7. Open the create-link form
  8. Style document links
  9. Show an icon before each link
  10. Show a card on hover
  11. Name pasted links after their page
  12. Gallery
  13. Documentation
  14. Part of docs.plus
  15. Contributing
  16. License

@docs.plus/extension-hyperlink

docs.plus Version Downloads License Discord

Typing docs.plus/demo makes a link with the site icon, and resting on it opens a hovercard. Mod-K on a selected word creates a link, and a pasted address takes its page title. Mod-K inside a link opens its preview, and Edit renames the link

Tiptap hyperlink mark with optional prebuilt popovers for creating, previewing, and editing links.

It replaces the @tiptap/extension-link mark that StarterKit v3 bundles.

Open in StackBlitz

Why use it

  • Autolink covers more than web URLs. As you type, bare domains get https://, emails become mailto:, and E.164 phones become tel:. App schemes from a catalog of 47, such as whatsapp://, autolink too.
  • The link UI is included. Prebuilt create, preview, and edit popovers ship with the package. Mod-k opens the create-link form with no configuration. Inside a link, Mod-k opens that link's preview when popovers.previewHyperlink is set, and Alt-Enter opens the link. Mod-click opens a link in a new tab. Every open uses a new tab until you set the navigate option. @tiptap/extension-link binds no keyboard shortcut.
  • Every boundary checks the scheme. isSafeHref blocks javascript:, data:, vbscript:, file:, and blob: on parse, import, write, serialize, and navigation. An embedded tab or newline cannot hide a scheme from the gate.
  • Your @tiptap/extension-link configuration ports over. isAllowedUri takes the same (uri, ctx) shape as @tiptap/extension-link, so an existing policy works unchanged. setLink, unsetLink, and toggleLink stay as aliases. [label](url) round-trips with @tiptap/markdown.

Install

npm install @docs.plus/extension-hyperlink

Or use pnpm add @docs.plus/extension-hyperlink, yarn add @docs.plus/extension-hyperlink, or bun add @docs.plus/extension-hyperlink.

Requires @tiptap/core ^3.32.2 and @tiptap/pm ^3.32.2. A Tiptap 3 release before 3.32.2 is not supported.

This package imports no React, Vue, or Next.js code. Use it from a plain page, Vite, React, Vue, Svelte, Next.js, Nuxt, or SvelteKit. Create the editor in the browser.

React Native has no DOM. Load the editor in a web view.

Installs two runtime dependencies, @floating-ui/dom and linkifyjs. The popover engine and the tooltip ship inside dist, so they add no third package.

Upgrading from 2.2? No export or option was removed, and stored JSON and Yjs documents load as before. Some behaviour changed, such as Mod-click, Mod-k inside a link and two HTML parse rules. See Migrating from 2.2 to 2.3.

Upgrading from 1.x? Option names, command names, and CSS class names all changed — see Migrating from 1.x.

Quickstart

Open in StackBlitz

Run this Quickstart in your browser first, with nothing to install. The app lives in examples/vanilla.

The host page needs one mount point: <div id="editor"></div>. The snippet also imports @tiptap/starter-kit. Add it with npm install @tiptap/starter-kit when your app has none yet.

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import {
  Hyperlink,
  createHyperlinkPopover,
  editHyperlinkPopover,
  previewHyperlinkPopover
} from '@docs.plus/extension-hyperlink'
import '@docs.plus/extension-hyperlink/styles.css'

const editor = new Editor({
  element: document.querySelector('#editor'),
  content: '<p>Try <a href="https://example.com">this link</a>.</p>',
  extensions: [
    // Disable StarterKit's bundled link mark — see Caveats.
    StarterKit.configure({ link: false }),
    Hyperlink.configure({
      popovers: {
        previewHyperlink: previewHyperlinkPopover,
        editHyperlink: editHyperlinkPopover,
        createHyperlink: createHyperlinkPopover
      }
    })
  ]
})

The styles.css import is optional. Without it, the prebuilt popovers render unstyled, and every popover shows with no fade, the hovercard included. The snippet also styles no document link — see Popovers and Style document links.

You should see a paragraph with one link. Click the link to open the preview popover, with copy, edit, and remove actions. Type example.org and a space, and the text becomes a link. Select a word and press Mod-k (Cmd on macOS, Ctrl elsewhere) to open the create-link form.

To show a card when the pointer rests on a link, add the hovercard option. See Show a card on hover.

Caveats

  • StarterKit.configure({ link: false }) is required, not stylistic. StarterKit v3 bundles @tiptap/extension-link by default. The two marks collide on the setLink / unsetLink / toggleLink command names. Your extensions array order then decides which mark each command writes, with no warning. If you keep Link only to load stored link marks, parsed and pasted HTML still gives hyperlink. This package's a[href] rule runs at priority 51, above Link's default 50.
  • A click on a link, and Mod-k inside a link, need popovers.previewHyperlink. The three openers, openPreviewHyperlink, openEditHyperlink and openCreateHyperlink, fall back to the prebuilt factory when a slot is null. The click handler and Mod-k do not. Leave previewHyperlink at null, and a click on a link opens no popover. Mod-k inside a link then does nothing, and it never opens the create form there.
  • linkOnPaste: false does not stop every paste from linking. The linkify paste rule stays registered, so a pasted bare URL still becomes a link. To stop that path too, set shouldAutoLink: () => false, which every autolink path consults.
  • HTMLAttributes.target reaches the DOM. HTMLAttributes: { target: '_blank' } does emit <a target="_blank">. A browser-driven target="_blank" navigation skips the click gate. Leave target at null and let the click handler open the link.
  • Markdown keeps only the link href. Export writes [text](href), and import reads the href alone, so a link title is lost both ways. A custom rel is lost too, and import sets the default rel. target, class and image never reach Markdown, by design.
  • A link icon needs CSS for .hyperlink-icon. Without styles.css or your own rule, the icon shows at its natural size. styles.css sizes it only inside .ProseMirror, so an icon from linkIconFor outside an editor needs your own rule. Without such a rule, a Tailwind host also breaks the line, because preflight sets img and svg to display: block. The styles.css rule wins over preflight inside .ProseMirror.
  • linkIcon must return a fresh element with no text. null gives a plain link with no error. Any other result, or a throw, gives a plain link and one console error for that linkIcon function.
  • A host mark above priority: 1000 splits one link into two icons. That mark renders outside the link. When it covers part of a link, the <a> splits in two, and each part gets an icon.
  • Hyperlink.extend({ addMarkView }) replaces the icon mark view. To keep the icon, call this.parent?.() and wrap its result.
  • Legacy link marks get no icon. Only the hyperlink mark renders through the icon mark view.
  • Parsed HTML drops every .hyperlink-icon element. The rule runs even with linkIcon unset, so content inside an element with that class is lost on load and paste.
  • A { light, dark } pair has limits. It needs styles.css or your own .hyperlink-icon size, or the span has zero width. A bad URL shows a blank 1em box. Browsers before Chrome 150, Firefox 150 and Safari 27 show the light image.
  • A transient image failure hides that icon until reload. This holds for an <img> icon from linkIconFetcher or from an image URL entry in linkIconMap. Each helper never requests a failed URL again, until the page reloads or you build a new helper. A { light, dark } pair keeps no such list.
  • A pointer already resting on a link gets no hovercard. The card starts only on pointerover. When a link renders under a still pointer, no card shows until the pointer leaves the link and comes back.
  • The hovercard sees only this bundle's popovers. It opens no card while a preview, create or edit popover of this package is mounted. A popover from another bundle, such as @docs.plus/extension-hypermultimedia, does not stop it.

The full list, with the reason for each, is in Caveats.

Common tasks

Every command lands on editor.commands and on editor.chain(). On editor.commands, each returns boolean, and false marks a no-op.

editor.chain().focus().setHyperlink({ href: 'https://example.com' }).run()
editor.getAttributes('hyperlink').href // read the current href

setHyperlink returns false when href is empty, when the composed gate rejects it, or when the schema cannot apply the mark there. See Commands for the full list.

Outside a link, Mod-k calls this command. Call it from your own toolbar button too:

editor.commands.openCreateHyperlinkPopover()

openCreateHyperlinkPopover opens UI, so editor.can().openCreateHyperlinkPopover() reports availability without mounting anything. With nothing selected, the prebuilt form inserts the typed URL as its own link text.

The styles.css stylesheet skins the popovers and the hovercard's tooltips. The mark renders a plain <a> with no class, so style your document links yourself:

Hyperlink.configure({ HTMLAttributes: { class: 'my-link' } })
a.my-link {
  color: #2563eb;
  text-decoration: underline;
}

For theme tokens, dark mode, and class names, see Styling.

linkIcon takes a sync function. It receives { href } and returns a fresh element, or null for a plain link:

Hyperlink.configure({
  linkIcon: ({ href }) => {
    const icon = document.createElement('span')
    icon.style.background = href.startsWith('https:') ? '#16a34a' : '#9ca3af'
    return icon
  }
})

The icon goes first inside each <a>, and the link text sits in a <span> after it. Only an href that passes isSafeHref reaches the function. validate and isAllowedUri do not run for icons. styles.css sizes the icon to 1em. See Options.

Two helpers build that function for you. linkIconMap is an offline table from domain to icon. linkIconFetcher builds one <img> per link from the link's origin, for example through your own favicon route. Chain them with ??, so a map hit wins and the fetcher helper never runs for it:

import { Hyperlink, linkIconFetcher, linkIconMap } from '@docs.plus/extension-hyperlink'

const icons = linkIconMap({ 'example.com': '/icons/example.svg' })
const favicons = linkIconFetcher({
  src: (origin) => `/favicon?origin=${encodeURIComponent(origin)}`
})
Hyperlink.configure({ linkIcon: (ctx) => icons(ctx) ?? favicons(ctx) })

linkIconFetcher takes two options: src and tile. src gets the link's origin, such as https://example.com or https://example.com:8080, with no path and no trailing slash. It must return an image URL string. Your route may answer with any image the browser can draw. If src throws, the link stays plain, and the package logs one console error. Links that share an icon URL share one request. Each <img> has alt="", a 16 × 16 fallback size and referrerPolicy="no-referrer". It stays hidden until it loads, so a failed load shows no icon. To skip a link, wrap the fetcher in your own function that returns null. tile: true paints a light square behind each icon on a dark color-scheme. On a light scheme the tile is clear. linkIconMap keys are bare hostnames: www. is ignored, a subdomain matches its parent key, and the href port does not count. Both helpers cover http: and https: links only. A mailto:, tel: or app link has no web origin, so each helper returns null for it. When every function in the ?? chain returns null, the link stays plain. To give those links an icon, chain your own function that reads href.

The favicons function above also works as the hovercard's siteIcon. A map entry may also be a { light, dark } pair of image URLs, or a function that returns an element. See Link icons.

Show a card on hover

The hovercard option shows a card when a mouse pointer rests on a link. Your fetchMetadata returns the title, description and picture, and the package makes no request of its own:

import { Hyperlink } from '@docs.plus/extension-hyperlink'
import '@docs.plus/extension-hyperlink/hovercard.css'
import '@docs.plus/extension-hyperlink/styles.css' // the card's tooltips

Hyperlink.configure({
  hovercard: {
    fetchMetadata: async ({ href }) => {
      const res = await fetch(`/metadata?url=${encodeURIComponent(href)}`)
      if (!res.ok) return null
      const page = await res.json()
      // Map your server's fields to HovercardData. `image` is `{ url }`, not a string.
      return {
        title: page.title,
        description: page.description,
        image: page.image ? { url: page.image } : undefined
      }
    }
  }
})

The built-in card shows the picture, the title, the description and a link line with Copy link. On an editable link whose text is its own URL, the line also shows Use page title. To draw your own card, add render. See Hovercard for the data contract, render, the timing and the keys.

The pasteTitle option swaps a pasted bare URL for the page title. The link appears at once with the URL, and your fetchTitle returns the title:

import { Hyperlink } from '@docs.plus/extension-hyperlink'

Hyperlink.configure({
  pasteTitle: {
    fetchTitle: async ({ href, signal }) => {
      const res = await fetch(`/metadata?url=${encodeURIComponent(href)}`, { signal })
      if (!res.ok) return null
      const data = await res.json()
      return typeof data.title === 'string' ? data.title : null
    }
  }
})

The swap is its own undo step, so one Mod-z restores the URL. Mod-Shift-v pastes the raw URL, and shouldFetch can follow a user setting. With no answer in 4 s, the URL stays. See Paste title for the rules, the title cleaning and a safe backend.

Prebuilt create, preview, and edit popovers (styles.css + popovers config), and the built-in hovercard (hovercard.css + hovercard option). Each screenshot has a light and a dark version, and follows your system preference.

Create — Mod+K

Create-link popover with URL field and Apply button

Preview — click a link

Preview popover on a link — copy, edit, and remove actions

Edit — preview → Edit

Edit-link popover with URL and text fields and Apply button

Hovercard — rest the pointer on a link

Built-in hovercard with the page picture, title, description, and a link line with Copy link

Documentation

GuideWhat it covers
API referenceOptions, commands, keyboard shortcuts, the full caveats list, server bundles, and TypeScript exports
PopoversThe three popover slots, factory option shapes, openers, and custom popovers
HovercardThe link card on hover: the built-in card, the data contract, custom render, timing, and keys
Paste titleA pasted URL named after its page: when it runs, the resolver contract, undo and a safe backend
StylingThe stylesheet, --hl-* theme tokens, dark mode, and class names
URL handlingHref normalization, scheme classification, and Markdown round-trip
SecurityThe dangerous-scheme gate at each boundary, and the exported safety helpers
AdvancedThe floating-popover primitive, the UI controller, and the tooltip primitive
MigrationWhat to check from 2.2 to 2.3, and the renamed options, commands, CSS classes, and popover APIs from 1.x
ChangelogEvery release and its breaking changes
For AI coding agentsThis README as plain Markdown for your agent. The docs are also on Context7

Part of docs.plus

This extension is built for and maintained by docs.plus. docs.plus is a free, real-time collaboration tool that lets communities organize knowledge hierarchically, with a chat thread on every heading. docs.plus runs these packages from source in production, so every release is exercised there before it reaches npm.

Contributing

Bug reports and PRs welcome. Setup, test commands, and the playground harness live in CONTRIBUTING.md.

License

MIT — see LICENSE.

版本与发布记录

@docs.plus/extension-hyperlink 共发布 22 个版本,首次发布于 2023/07/18;过去 12 个月发布了 5 个版本。

版本发布日期距今
2.3.0latest2026/11/103 天前
2.2.02026/10/2222 天前
2.1.02026/10/131 个月前
2.0.02026/08/113 个月前
4.3.02026/04/196 个月前
1.5.22023/11/013 年前

关于 @docs.plus/extension-hyperlink 的常见问题

@docs.plus/extension-hyperlink 是什么?

Tiptap extension for hyperlinks with built-in popovers, auto-linking, and 50+ URL scheme support

如何安装 @docs.plus/extension-hyperlink?

在项目目录运行 npm install @docs.plus/extension-hyperlink,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@tiptap/pm(^3.32.2)、@tiptap/core(^3.32.2)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@docs.plus/extension-hyperlink 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@docs.plus/extension-hyperlink 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@docs.plus/extension-hyperlink 可以商用吗?

@docs.plus/extension-hyperlink 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@docs.plus/extension-hyperlink 的下载量有多少?

根据 npm 官方下载统计,@docs.plus/extension-hyperlink 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,285 次,日均约 76 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@docs.plus/extension-hyperlink 还在维护吗?

最新版本 2.3.0 发布于 2026/11/10(3 天前);至今共发布 22 个版本,其中过去 12 个月发布了 5 个;首次发布于 2023/07/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。