How to parse a Link header

By

Parse an HTTP Link header in the browser. Each target, relation type, and title is listed, and a relative target resolves when you give the response URL.

Free toolThis tutorial uses the Link Header Parser. It runs in your browser, so open it in another tab and follow along.

An HTTP response can point at another resource without putting that pointer in the body. The Link header does that. A blog uses it for the previous and next page. A feed uses it for rel="alternate". GitHub uses it so a client can walk a paginated API without guessing the next URL.

The field is defined in RFC 8288. One header value is a comma-separated list. Each item is a URI in angle brackets, then semicolon-separated parameters. rel is the parameter that says what the link means. The Link header parser reads that list in the browser.

How a Link header is parsedThe page unfolds continuation lines, splits link-values on commas, and requires rel. A relative target is resolved only when you type the response URL.NoYesYesNoLink header textUnfold continuationlinesSplit on commas into<target> valuesDoes the value haverel?Note, and skip itOne link perrelation typeBase URL typed?Resolve a relativetargetLeave the target aswritten

A single link looks like this:

</blog/>; rel="next"

The target is /blog/. The relation type is next. Quotes around the relation type are optional when the value is a token. Parameter names are case-insensitive, so Rel and rel are the same parameter.

RFC 8288 says a link with no rel is not usable. This page drops it and says why. Several relation types can share one target. You write them in one quoted string, separated by spaces. The page then emits one link object per type, because that is how a client is supposed to look them up.

title is a human label. title* is the same label in the RFC 8187 encoding, which can carry a language and non-ASCII bytes. When both are present, title* wins. rev is the old reverse relation. The RFC deprecates it. The page still shows it, and it adds a note.

A relative target is relative to the URL of the response that carried the header. The page does not know that URL unless you type it. Leave the base empty and the target stays as it was written. Type https://flaviocopes.com and /blog/ becomes https://flaviocopes.com/blog/.

Parse the sample

Open the Link header parser. Click Load sample. The sample is a wrapped header. The second line starts with spaces, which is the old obs-fold rule: a continuation line joins the line above it. The two link-values are separated by a comma. Each title* value is the example from RFC 8288, pointed at paths on this site.

Link: </blog/page/>;
      rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
      </blog/page/2/>;
      rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

The base URL in the sample is https://flaviocopes.com. The page prints:

Links: 2

1. rel: previous
   target: https://flaviocopes.com/blog/page/
   title: letztes Kapitel (de)

2. rel: next
   target: https://flaviocopes.com/blog/page/2/
   title: nächstes Kapitel (de)

Here is how the first title is computed. title* is three pieces separated by single quotes: the charset UTF-8, the language de, and the bytes letztes%20Kapitel. %20 is a space. The decoded text is letztes Kapitel, and the language is kept in parentheses. The second title is n%c3%a4chstes%20Kapitel. The bytes c3 a4 are UTF-8 for ä, so the text is nächstes Kapitel.

The targets were /blog/page/ and /blog/page/2/. The base has no path that matters here, because the references start with /. new URL("/blog/page/", "https://flaviocopes.com") is https://flaviocopes.com/blog/page/. A reference that did not start with / would be resolved against the base path instead.

The leading Link: is stripped, in any mix of capitals. You can paste the value alone and get the same links.

Two header lines are two fields. Paste both, with a base of https://flaviocopes.com:

Link: </a/>; rel="next"
Link: </b/>; rel="prev"

You get rel: next at https://flaviocopes.com/a/ and rel: prev at https://flaviocopes.com/b/. A continuation line is different. It starts with a space or a tab, and it is joined to the line above with a single space. A second Link: at the start of a line starts a new field. That matches a response where the server sent the header twice. Order follows the text you pasted, top to bottom.

Two relation types on one target

This value uses one target and two types:

<https://flaviocopes.com/>; rel="start https://flaviocopes.com/rel/archive"

The page prints:

Links: 2

1. rel: start
   target: https://flaviocopes.com/

2. rel: https://flaviocopes.com/rel/archive
   target: https://flaviocopes.com/

start is a registered relation type. The second type is an extension URI. Both are lowercased. The note is: rel lists more than one type, so each type is a separate link. The target is already absolute, so it does not change when you add a base.

Registered names and extension URIs are both lowercased, including the letters in the URI. If you need the original spelling of an extension URI, it is not preserved.

What goes wrong

Paste </blog/>; rel="next" and leave the base empty:

Links: 1

1. rel: next
   target: /blog/

The note is: A target is relative and no base URL was given, so it was not resolved. That is the whole rule. The page will not guess https://flaviocopes.com.

Paste </blog/> with no rel:

A target had no rel, so it was not emitted. RFC 8288 requires rel.

The summary says Links: 0 only when something else was emitted. With nothing emitted, the page reports that note as the error.

A base that is not absolute fails before any link is read:

The base URL is not absolute.

/blog/ is not a base. https://flaviocopes.com/blog/ is.

rev is still parsed. <https://flaviocopes.com/>; rel="next"; rev="prev" produces a link with rel: next and an attribute rev: prev, plus the note: rev is deprecated in RFC 8288.

If both titles are present, title* replaces title and the note says title* replaced title. A repeated title, title*, media, or type keeps the first one. The later copy is ignored, with a note.

A quoted parameter uses backslash escapes. title="a \"quote\"" is the text a "quote". An ext-value that is not charset'language'bytes is skipped, and the note names the parameter.

A base with a path changes a relative reference that does not start with /. Against https://flaviocopes.com/blog/page/, the reference 2/ becomes https://flaviocopes.com/blog/page/2/. The reference page/2/ becomes https://flaviocopes.com/blog/page/page/2/, because the last segment of the base is a directory only when the base ends in a slash, and page is then another segment. A reference that starts with / ignores the base path. /blog/ against that same base is https://flaviocopes.com/blog/.

anchor is the context the relation is from, and it is resolved the same way. This header:

</blog/>; rel="next"; type=text/html; anchor="/author/"

with base https://flaviocopes.com/blog/page/ prints:

Links: 1

1. rel: next
   target: https://flaviocopes.com/blog/
   anchor: https://flaviocopes.com/author/
   type: text/html

type is a hint about the media type of the target. It is not a check. The page does not fetch the target, so it cannot tell you whether /blog/ is actually HTML.

The comma between link-values is required by the examples in RFC 8288 section 3.5. The appendix algorithm, read literally, returns at the first comma without consuming it and then stops. This page follows the examples and the list rule, so the second link in the sample is not dropped.

Only the base is written into the page URL, as ?base=. The header can be long and can contain URLs you do not want in a browser history, so it stays in the text box.

When the page is the wrong tool

Use this page when a response already has a Link header and you want the targets and the relation types. Pagination, rel="alternate", and a title* in another language are the cases it is built for.

Use the HTTP headers explainer when you have a pile of headers and you want a one-line description of each name. It will not split a Link value into targets. The content negotiation page reads Accept and Accept-Language. Those are different fields. The cookie builder reads Set-Cookie, which has its own grammar and its own date rules.

The HTML <link> element uses the same relation types and a different syntax. rel="next" in a document head is not a Link header. The HTTP response headers list shows the header form, Link: </feed>; rel="alternate". HTTP headers in Express is the place to set response headers from a route. Once the response is on the wire, paste the Link line here.

Try the Link Header Parser

Paste a Link header and list each target, relation type, and title. Relative targets resolve when you give the response URL. Parsing stays in the browser.

Open the Link Header Parser →

  • HTTP Response Headers Explainer: Paste response headers and get each one explained — plus warnings for missing security headers like HSTS and CSP.
  • Content Negotiation Helper: Paste an Accept header, list what your server offers, and see which MIME type wins — plus Accept-Language and Accept-Encoding.
  • Cookie Inspector & Set-Cookie Builder: Paste a Cookie or Set-Cookie header to inspect it, or build a Set-Cookie line with SameSite, HttpOnly, Secure, and Partitioned — all in the browser.

More tool tutorials →

Want me to talk about your product? You can sponsor this site.