Library / SDK
markdown-it/linkify-it avatar
markdown-it/linkify-it

linkify-it and the problem of deciding where a URL starts

Links recognition library with full unicode support

740 stars73 forksTypeScriptMIT

At a glance

What is it?
linkify-it is a MIT licensed TypeScript library that finds links in plain text, including URLs without a scheme and fuzzy email addresses, with Unicode and international domain support. Its real work is deciding what is not a link, which is where the option set does the interesting work.
Who is it for?
linkify-it suits a project that needs to turn bare text into clickable links, especially one dealing with non-Latin scripts, international domains or an existing markdown-it pipeline, since it is the library markdown-it itself builds on and ships with a per-match schema, url and raw text breakdown. It does not suit a project that wants a hosted detection service, or one that expects the library to resolve or fetch what it finds, because the output stops at a match object.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A linkifier is mostly a classifier, and this one admits it

linkify-it describes itself as a links recognition library with full Unicode support, focused on high quality link patterns detection in plain text. That phrase, high quality link patterns detection, is the whole job in two words.

Finding a URL after the word http is a regular expression exercise. Deciding whether the word example.com in the middle of a sentence is a link, whether a version string like 1.2.3.4 is an address, whether a trailing dot is part of the hostname, whether an email address in a Cyrillic domain is real, those are classification decisions with no answer that satisfies everyone.

The library handles the case where the link is already marked up, such as http(s)://, ftp://, mailto: and protocol neutral // links, and it handles the case where nothing is marked up, through what it calls fuzzy links. Between those two poles sits the tuning surface, and the option list in the API section is where a user actually spends time.

The output shape is worth noting because it is richer than a boolean. Each match carries the schema, the offset of the matched text, the index of the next character after the match, the raw text as written, the normalized text, and the generated url. The raw and normalized split is the detail that matters downstream, because it lets you display what the user typed while linking to a cleaned up address.

Installing and the first call, in the project's own example

The package installs from npm with one command:

bash
npm install linkify-it --save

The usage example in the project guide shows the shape of a typical setup, where a LinkifyIt instance is created with options, extended with methods, and then queried against a string:

js
import { LinkifyIt } from 'linkify-it';
const linkify = new LinkifyIt({ fuzzyLink: true });

linkify
  .tlds(require('tlds'))
  .tlds('onion', true)
  .add('ftp:', null)
  .set({ fuzzyIP: true });

console.log(linkify.test('Site github.com!'));
// true

Every method in that chain returns the linkifier, so configuration is a fluent sequence rather than a separate configuration object, and .set() overrides options later without touching the properties that were not named.

The match output for the same string shows what the library actually returns:

js
console.log(linkify.match('Site github.com!'));
// [ {
//   schema: "",
//   index: 5,
//   lastIndex: 15,
//   raw: "github.com",
//   text: "github.com",
//   url: "http://github.com",
// } ]

Notice that the schema is empty for a fuzzy link and that a scheme has been added to the url. The raw and text values are identical here because there was nothing to normalize.

The fuzzy options are where the false positives live

Four options decide how aggressive the detection is, and each one trades recall against false positives.

fuzzyLink recognizes URLs without an http(s):// head and defaults to false. Turning it on is what makes the example above find github.com in a sentence. fuzzyIP allows IP addresses in fuzzy links and also defaults to false, and the guide is explicit that it can conflict with texts like version numbers. That warning is the honest admission that a bare 192.168.0.1 in a log line looks exactly like a host.

fuzzyEmail recognizes emails without a mailto: prefix and defaults to true, so by default an address in plain text is already a match. The remaining option, urlAuth, recognizes authentication data in URLs and defaults to false, which keeps user:password@host segments out of generated links unless you ask for them. Two more options bound the behaviour: the --- flag, set to true to terminate a link with a triple dash when it would be read as a long dash, and maxLength, the maximum link length, defaulting to 10000.

A useful detail: fuzzyEmail being on by default while fuzzyLink is off by default means the library links emails in a document that contains no bare domains. That asymmetry is deliberate, since a bare email address is far less likely to be a false positive than a bare domain.

The TLD list is the false positive filter for fuzzy links

A fuzzy link cannot be recognised by shape alone, because anything that looks like a word with a dot could be a hostname. The library solves this with a list of top level domains, and the .tlds() method loads or merges one.

The defaults are deliberately conservative. Two-letter root zones are accepted, a specific set of longer zones is accepted, namely biz, com, edu, gov, net, org, pro, web, xxx, aero, asia, coop, info, museum, name, shop and the Cyrillic рф zone, and encoded xn--... root zones are accepted. Anything not on that list is not treated as a fuzzy link unless the list is replaced.

The example in the guide loads the full list from the tlds package and then adds onion while keeping the old entries, since .tlds(list, keepOld) merges rather than replaces unless the second argument is false. Replacing the default list outright is the documented way to tighten detection, because a short whitelist of common zones produces far fewer false positives on prose that mentions file extensions or abbreviations.

The international domain support referenced in the project description is what the encoded xn--... allowance buys, and the explicit рф entry shows the list is maintained with non-Latin zones in mind rather than derived mechanically from ASCII.

Rules are extensible through .add, and can be switched off entirely

The extensibility mechanism is .add(key, value), where the key is a link prefix, usually a protocol name with a colon, such as skype:. The library checks that the prefix is not preceded by an alphanumeric character, which stops a prefix inside a word from triggering a match.

The value is the rule, and it has two parts. The validate function receives the text, a position that is the index right after the link prefix, and a self reference, and it returns the length of a match starting at that position. The normalize function is optional and normalizes the text and url of a matched result, which is the hook a Twitter mention rule would use.

The example in the guide adds ftp: with a null value. Passing null disables an existing rule, so that line is not adding a rule but removing one, since ftp links are understood by default. That is the interesting half of the API: it is as easy to take a protocol away as to add one.

The rebuilder option lets you supply a custom REBuilder instance for patched regex fragments, which is what a caller would reach for when a default pattern needs to be adjusted without forking the library.

Querying at a position, and the limits of the position-based API

Beyond test and match there are three narrower queries, and each covers a case the broad ones do not.

.testSchemaAt(text, name, offset) checks only a specific protocol tail at exactly the given position and returns the length of the found pattern, zero on failure. .matchAtStart(text) reports whether a match exists at the start of the string and returns a match or null, and the guide says plainly that it does not work with fuzzy links. Those two limits are not incidental, they come from how a start-anchored match cannot be sure it is not the tail of a longer word, and fuzzy links have no scheme to anchor on.

The practical use is incremental parsing. A chat application or an editor that wants to link a URL as the user finishes typing needs to know whether the candidate at the cursor is a link, and testSchemaAt with an offset answers that without scanning the whole document. The cost is that these entry points are less forgiving than match, and a caller who does not read the fuzzy caveat will get a null for a bare domain at the start of a line.

The library also exposes a normalizer for the text, and every match carries both raw and text, which is what allows a rule to map a Twitter mention to a profile url while keeping the visible string as the user typed it.

A small MIT package with a build pipeline and no releases

linkify-it is MIT licensed and published under the markdown-it organisation, with funding links to both a maintainer and the markdown-it sponsors page. The current version in package.json is 6.1.0, and the project has no GitHub releases, so the package version is the only release identifier a consumer can pin to.

The distribution layout is modern and deliberate. The main field points at build/index.cjs.js for CommonJS, the module field at build/index.mjs for ESM, and the types field at build/index.d.ts, with an exports map covering types, require and import conditions, plus a package.json export. Only the build/ directory is published to npm, and the package is authored in TypeScript with tsconfig.json and typedoc.json in the repository for generated API documentation.

The scripts tell you how the project is verified. The test script runs lint, then build, then type-check, and only then the test suite under coverage with c8, so a test run also proves the distribution builds and the types compile. The build itself calls support/build_dist.mjs and support/build_demo.mjs, and the demo is published to GitHub Pages after every publish, which is what keeps the hosted demo in step with the published package.

There is one runtime dependency, uc.micro, and the rest are development tools including rollup, typedoc and vite. The last push to the repository was on 2026-10-18.

Editorial conclusion

linkify-it suits a project that needs to turn bare text into clickable links, especially one dealing with non-Latin scripts, international domains or an existing markdown-it pipeline, since it is the library markdown-it itself builds on and ships with a per-match schema, url and raw text breakdown. It does not suit a project that wants a hosted detection service, or one that expects the library to resolve or fetch what it finds, because the output stops at a match object. Before adopting it, read the fuzzyLink and fuzzyIP defaults closely, because enabling fuzzyIP can misread version numbers as hosts, and note that the package has no GitHub releases, so the version to depend on is 6.1.0 in package.json, with the last push to the repository on 2026-10-18.

Frequently asked questions

What does linkify-it do?

linkify-it detects links in plain text, including URLs with a scheme, protocol neutral // links, fuzzy links without a scheme, and fuzzy email addresses. Each match reports the schema, offsets, raw text, normalized text and the generated url.

How do I install and use linkify-it?

Install it with npm install linkify-it --save, create an instance with new LinkifyIt(options), then call test(text) for a boolean or match(text) for the array of match objects. Configuration methods such as tlds, add and set chain onto the instance.

Why does linkify-it not link a bare domain by default?

The fuzzyLink option, which recognizes URLs without an http(s):// head, defaults to false. Fuzzy links also need a recognized top level domain, and the default list accepts two-letter zones, a fixed set of longer zones and encoded xn--... zones. You can load a fuller list with .tlds().

Can I add or remove a protocol in linkify-it?

Yes. The .add(key, value) method takes a prefix such as skype: and a rule object with a validate function and an optional normalize function. Passing null as the value disables an existing rule, which is how the ftp: protocol is turned off in the project example.

Does linkify-it support Unicode and international domains?

Yes, full Unicode support including astral characters is stated as the core feature, and international domains are supported through encoded xn--... root zones, with рф listed among the default accepted zones. The examples folder includes a file demonstrating CJK paired brackets in URL paths.

Official sources

  1. Issues
  2. License: MIT
  3. markdown-it/linkify-it on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/markdown-it-linkify-it.svg)](https://hysenlabs.com/projects/markdown-it-linkify-it)

Was this useful?