terminal-link turns a URL into a clickable terminal link, and a plain string when it cannot
Create clickable links in the terminal
At a glance
- What is it?
- terminal-link is a small npm package by Sindre Sorhus that renders OSC 8 hyperlink escape sequences for stdout and stderr, with a text fallback for terminals that do not support them. It is ESM only, requires Node.js 20 or later, and depends on ansi-escapes and supports-hyperlinks.
- Who is it for?
- Adopt terminal-link if you publish a Node.js command line tool and want URLs to be clickable without hand writing escape sequences. Do not adopt it if you need CommonJS support, since the package is ESM only and the engines field requires Node.js 20 or later.
- 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 35 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A link is an escape sequence wrapped around two strings
The whole API fits in one call. terminalLink takes a display text and a URL and returns a string that you print, and the package is concerned with stdout specifically:
import terminalLink from 'terminal-link';
const link = terminalLink('My Website', 'https://sindresorhus.com');
console.log(link);The returned value is not a special object with a render method. It is a string containing an escape sequence around the text and the URL, which means it can be composed into larger output, passed to a logger, or concatenated with other styled fragments before it reaches the terminal.
The package is a single module. The repository holds index.js, index.d.ts, a test file, a type test in index.test-d.ts, and a package.json whose files array ships only index.js and index.d.ts. There is no build step to speak of and no source directory to compile, which matches a package whose entire job is emitting correctly formatted escape sequences.
Two runtime dependencies carry the weight: ansi-escapes, which holds the escape sequence definitions, and supports-hyperlinks, which decides whether the current terminal can handle them.
The fallback is the design decision most tools will get wrong
Hyperlinks in a terminal are an escape sequence that not every terminal understands, and the failure mode when you ignore that is a screen full of noise. terminal-link handles it by checking first and degrading.
The README states that for unsupported terminals the link is printed as plain text with a space separator, giving `My website https://sindresorhus.com`. That default is the point of the package: a CLI can call the same function everywhere and get either a clickable link or a readable line, with no branching in the calling code.
The `fallback` option overrides that behaviour. It accepts a function or a boolean, the function receives the `text` and `url` and is expected to return a string, and setting it to `false` means the `text` is returned as-is when the terminal is unsupported. Returning only the text is a defensible choice for a tool whose output is piped into another program, where a duplicated URL adds noise to a machine readable stream.
There is also a way to check support directly. terminalLink.isSupported is a boolean for whether the terminal's stdout supports links, and terminalLink.stderr.isSupported is the equivalent for stderr. The README advises preferring the default fallback or the fallback option over checking these yourself, which is good advice, since it keeps the support decision inside the package where it can be updated when terminal behaviour changes.
stderr gets its own entry point, because output streams are not interchangeable
terminalLink.stderr(text, url, options?) creates a link for use in stderr, and it takes the same arguments as terminalLink(). The separation is not cosmetic. A progress bar or a spinner is normally written to stderr while the actual result goes to stdout, and a link embedded in the wrong stream ends up in the wrong place or gets cleared by a redraw.
The same split applies to the support checks, with terminalLink.stderr.isSupported reporting whether stderr supports links. The README gives the same preference guidance for it: use the fallback rather than branching on the flag.
What the API does not offer is a way to combine both into one call with a stream argument, and there is no option for styling the link text itself. Colouring the visible text is the job of a separate package in this family, chalk for terminal string styling, and the Related section also points at terminal-link-cli, a command line front end for this module, and ink-link, a link component for Ink.
ESM only, Node.js 20 and later, no side effects
The package.json makes the platform requirements explicit. The type field is module, the exports map points types at ./index.d.ts and default at ./index.js, and the engines field requires Node.js 20 or later. There is no CommonJS build in the files array, so a require() from a CommonJS project will not resolve, and a project still on Node.js 18 cannot install it cleanly.
The sideEffects field is set to false, which lets a bundler drop the module entirely when nothing imports it. For a package that only formats a string and holds no state, that is the correct declaration, and it means adding it to a CLI's dependency tree costs nothing at runtime when the import is tree shaken away.
TypeScript definitions ship with the package and the exports map makes them the resolved types, so a TypeScript consumer gets the signatures without a separate @types package. The test script runs xo for linting, ava for the unit tests and tsd for the type tests, and the ava configuration sets serial to true, so the suite runs its cases one at a time rather than concurrently.
Where this is the wrong tool
The obvious limit is scope. terminal-link formats one link. If you want a block of output where a URL, a file path and a line number are all separately clickable, you are assembling escape sequences yourself or reaching for a different package, and this one will not cover the case.
The second limit is the detection dependency. Whether the output is actually a terminal, which terminal emulator is running, and whether the user's settings allow hyperlinks are all decided by supports-hyperlinks. A CI log collector, a tmux session with older settings or a terminal that is not on the supported list will all land in the fallback path, so the clickable behaviour you are adding is not something you can promise in a README without qualifying it.
The third limit is that the module writes escape sequences into whatever stream you give it, whether or not that stream is a terminal. Redirecting output to a file therefore leaves escape codes in the file. The package does not check that for you, so a CLI that supports both human and machine output has to decide where the check happens, and the README does not document a mechanism for that beyond the fallback option.
The alternative in the same family is terminal-link-cli, which exposes this module as a command line tool rather than a library, and the upstream ecosystem option is to write the OSC 8 sequence yourself using ansi-escapes directly, which is what terminal-link does internally.
Version 5 is a small module with a slow release history
The version history is short. v3.0.0 was published on 2021-04-18, v4.0.0 on 2025-03-25 and v5.0.0 on 2025-09-08. The last push was on 2026-09-18 and the repository is not archived.
That pattern fits a package with a small, stable surface. The API is a function, a stderr variant, two support flags and a fallback option, and the major bumps came with the ecosystem changes around ESM and Node version requirements rather than with new behaviour. The v5 release date of 2025-09-08 lines up with the current package.json declaring version 5.0.0 and the Node.js 20 requirement, so the last major bump was where the platform floor moved.
The licence is MIT, which is recorded in the package.json and in a license file at the repository root. For a dependency that only formats strings and pulls in ansi-escapes and supports-hyperlinks, the licence question is not likely to be the deciding factor.
The cost side of adopting it is the dependency floor, not the code. Requiring Node.js 20 and ESM is the real constraint for a library used by other CLI authors, since it narrows the set of projects that can depend on you and forces a major version bump when the floor eventually moves again.
Editorial conclusion
Adopt terminal-link if you publish a Node.js command line tool and want URLs to be clickable without hand writing escape sequences. Do not adopt it if you need CommonJS support, since the package is ESM only and the engines field requires Node.js 20 or later. Verify first that your output is a terminal rather than a redirected file, since the escape sequences are written either way, and decide what your fallback string should look like, because the default joins the text and the URL with a space and the fallback option lets you replace or suppress it.
Frequently asked questions
How do I create a clickable link in the terminal with terminal-link?
Import the default export and call it with the display text and the URL, then print the result: import terminalLink from 'terminal-link' followed by terminalLink('My Website', 'https://sindresorhus.com'). The returned string contains the escape sequence for the link.
What does terminal-link do on a terminal without hyperlink support?
By default the link is printed as plain text with a space separator, so the output reads as the text followed by the URL. You can override that with the fallback option, which accepts a function returning a string, or set it to false to return only the text.
Can terminal-link write a link to stderr?
Yes. terminalLink.stderr(text, url, options?) creates a link for use in stderr with the same arguments, and terminalLink.stderr.isSupported reports whether stderr supports links.
What Node.js version does terminal-link require?
The engines field requires Node.js 20 or later. The package is ESM only, with type set to module and a single exports entry pointing at index.js, so it cannot be required from CommonJS.
Which dependencies does terminal-link use?
Two runtime dependencies: ansi-escapes for the escape sequence definitions and supports-hyperlinks for detecting whether the terminal can render them. The dev dependencies are ava, tsd and xo, and the test script runs all three.
Official sources
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.
[](https://hysenlabs.com/projects/sindresorhus-terminal-link)Was this useful?