- Scan ID:
- 95245354-d30c-48fc-8c4a-f6245658a5eaFinished
- Submitted URL:
- https://relay.dev/graphql/connections.htm
- Report Finished:
Links · 2 found
The outgoing links identified from the page
Link | Text |
---|---|
https://graphql.org/learn/pagination/ | pagination best practices |
https://spec-md.com | Spec Markdown |
JavaScript Variables · 3 found
Global JavaScript variables loaded on the window object of a page, are variables declared outside of functions and accessible from anywhere in the code within the current scope
Name | Type |
---|---|
onbeforetoggle | object |
documentPictureInPicture | object |
onscrollend | object |
Console log messages · 1 found
Messages logged to the web console
Type | Category | Log |
---|---|---|
error | network |
|
HTML
The raw HTML body of the page
<!DOCTYPE html><!-- Built with spec-md https://spec-md.com --><html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"><title>GraphQL Cursor Connections Specification</title>
<style>/** * prism.js default theme for JavaScript, CSS and HTML * Based on dabblet (http://dabblet.com) * @author Lea Verou */ code[class*="language-"], pre[class*="language-"] { color: black; background: none; text-shadow: 0 1px white; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-size: 1em; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.5; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { text-shadow: none; background: #b3d4fc; } pre[class*="language-"]::selection, pre[class*="language-"] ::selection, code[class*="language-"]::selection, code[class*="language-"] ::selection { text-shadow: none; background: #b3d4fc; } @media print { code[class*="language-"], pre[class*="language-"] { text-shadow: none; } } /* Code blocks */ pre[class*="language-"] { padding: 1em; margin: .5em 0; overflow: auto; } :not(pre) > code[class*="language-"], pre[class*="language-"] { background: #f5f2f0; } /* Inline code */ :not(pre) > code[class*="language-"] { padding: .1em; border-radius: .3em; white-space: normal; } .token.comment, .token.prolog, .token.doctype, .token.cdata { color: slategray; } .token.punctuation { color: #999; } .token.namespace { opacity: .7; } .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: #905; } .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #690; } .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #9a6e3a; /* This background color was intended by the author of this theme. */ background: hsla(0, 0%, 100%, .5); } .token.atrule, .token.attr-value, .token.keyword { color: #07a; } .token.function, .token.class-name { color: #DD4A68; } .token.regex, .token.important, .token.variable { color: #e90; } .token.important, .token.bold { font-weight: bold; } .token.italic { font-style: italic; } .token.entity { cursor: help; }</style>
<style>:root{color:#333;font-family:Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;font-size:15px;line-height:1.5;--mono-font-size: 13px;--indent: 1rem;--list-indent: 1.5rem;--dfn-indent: 0rem}@media (min-width: 720px){:root{font-size:17px;--mono-font-size: 15px;--indent: 2rem;--list-indent: 2rem;--dfn-indent: 2rem}}body{margin:3rem 0 3rem}article{margin:0 1rem}@media (min-width: 720px){body{margin:6rem auto 3rem;max-width:800px;padding-left:75px;padding-right:clamp(0px,calc((100vw - 800px) * .25),75px)}}.source-link{display:none}@media screen and (min-width: 720px){.source-link{display:block;position:absolute;width:18px;fill:#ccc;opacity:.3}.source-link:hover{opacity:1}}.outdated-selection-link,.selection-link{position:absolute;display:block;color:#fff;--selection-background-color: #cacee0;background:var(--selection-background-color);border-radius:4px;font-size:36px;height:23px;line-height:48px;text-align:center;text-decoration:none;width:25px;user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.outdated-selection-link:hover,.selection-link:hover{text-decoration:none}.outdated-selection-link:before,.selection-link:before{border:5px solid transparent;content:"";height:0;margin-top:-5px;margin-right:-5px;position:absolute;right:1px;top:50%;width:0}@media (max-width: 719px){.outdated-selection-link:before,.selection-link:before{border-bottom-color:var(--selection-background-color);border-top:0;right:50%;top:1px}}@media (min-width: 720px){.outdated-selection-link:before,.selection-link:before{border-left-color:var(--selection-background-color);border-right:0;right:1px;top:50%}}.selection-link:hover{--selection-background-color: #3b5998}.outdated-selection-link{--selection-background-color: #f0babe;font-size:21px;font-weight:800;line-height:27px}.outdated-selection-link:hover:after{content:"This selection content has changed since this link was created.";font:9pt/11pt Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;position:absolute;display:block;white-space:nowrap;padding:2px 5px 1px;top:-20px;background:black;color:#fff}a{color:#3b5998;text-decoration:none}a:hover{text-decoration:underline}img{max-width:100%}dl{margin:1rem 0 1rem var(--dfn-indent)}dd{margin:.25em 0 .5em var(--indent)}dd+dd{margin-top:1rem}dfn,.spec-ref{font-style:italic}dfn>a,.spec-ref>a{color:inherit}h1,h2,h3,h4,h5,h6{font-weight:bold;margin:3em 0 1em;position:relative}@media (min-width: 720px){header>h1{margin:6em 0 3em}}h1{font-size:1.5em;margin-top:5em}h2,h3{font-size:1.25em}h4,h5,h6{font-size:1em}section{padding-top:1rem;margin-top:-1rem}section.subsec>h6{margin-top:2em}section.subsec>h6>a{color:#333}section .spec-secid{margin-right:1ex}@media (min-width: 720px){section .spec-secid{position:absolute;right:100%;text-align:right;white-space:nowrap}}footer{font-size:75%;opacity:.5;text-align:center;margin-top:12rem}.spec-toc{margin:1rem 0 3rem}.spec-toc .title{content:"Contents";display:block;font-weight:bold;margin:5em 0 1em}.spec-toc .spec-secid{margin-right:1ex}.spec-toc ol{list-style:none;padding-left:0;margin-top:0;margin-bottom:0}.spec-toc ol ol{list-style:none;padding-left:2ex;margin-bottom:.25em}.spec-toc li{position:relative;padding:5px 0 0 30px;margin:-5px 0 0 -30px}.spec-toc a{color:#333}.spec-toc a:hover{text-decoration:none}.spec-toc a .spec-secid{color:#3b5998}.spec-toc a:hover .spec-secid{text-decoration:underline}.spec-toc .toggle{display:none}.spec-toc .toggle+label{cursor:pointer;left:6px;opacity:1;padding:5px 6px 5px 7px;position:absolute;top:6px;transform:rotate(0deg);transition:all .18s ease-in-out}.spec-toc .toggle+label:after{border-color:transparent transparent transparent #bbc;border-style:solid;border-width:6px 0 6px 7px;content:" ";display:block;height:0;width:0}@media (pointer: fine){.spec-toc .toggle+label{left:10px;padding:3px 5px 3px 6px;top:8px}}.spec-toc .toggle:checked+label{transform:rotate(90deg)}@media (hover: hover){.spec-toc li:not(:hover)>.toggle:checked+label{opacity:0}}.spec-toc .toggle:not(:checked)~ol{max-height:0;overflow:hidden;margin:0}.spec-sidebar-toggle{display:none}.spec-sidebar-toggle+label>.spec-sidebar-button{position:fixed;right:0;top:0;padding:10px 15px;font-size:30px;color:#000000b3;z-index:2;cursor:pointer;user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.spec-sidebar-toggle:checked+label:after{content:"";position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:0}.spec-sidebar{display:none;position:fixed;right:0;top:0;width:min(320px,calc(100vw - 48px));font-size:14px;line-height:1.75;overflow-y:scroll;height:100%;padding:0 0 5rem 30px;box-sizing:border-box;background:#f0f0f0;box-shadow:inset 1px 0 rgba(0,0,0,.05),-4px 0 8px -2px rgba(0,0,0,.04);overscroll-behavior:contain}.spec-sidebar{user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.spec-sidebar-toggle:checked~.spec-sidebar{display:block}.spec-sidebar .viewing>a:after{color:#8b9;content:"\2022";margin-left:1ex}@media (min-width: 1220px){.spec-sidebar-toggle+label{display:none}.spec-sidebar{display:block;box-shadow:inset 1px 0 rgba(0,0,0,.05),inset 4px 0 8px -2px rgba(0,0,0,.08)!important}body{padding-right:345px}}.spec-note{background:#FEFEF3;border-left:solid 4px #F4E925;margin:1rem -1rem;min-width:70vw;padding:8px 1rem 12px calc(1rem - 4px);width:-moz-fit-content;width:-webkit-fit-content;width:fit-content}@media (min-width: 720px){.spec-note{min-width:416px}}.spec-note>a:first-child{color:#6c6613;display:block;font:italic 11pt/18pt Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;opacity:.6;user-select:none}.spec-todo{color:#666;margin:1em 0 1em 5em;min-height:1em}.spec-todo::before{content:"todo";display:block;float:left;margin-left:-5em;text-transform:uppercase}.spec-index ol{list-style-type:none;margin:0 0 0 var(--indent);padding:0;column-width:210px;column-gap:var(--indent)}.spec-index ol li{width:min-content;white-space:nowrap}pre,code{font-family:Consolas,Monaco,"Andale Mono","Ubuntu Mono",monospace;font-size:var(--mono-font-size);font-weight:inherit}code{background:rgba(0,0,0,.03);margin:-2px -1px;padding:3px 3px;white-space:pre-wrap}pre>code{background:none;font-weight:inherit;margin:0;padding:0;white-space:pre}pre{background:#FAFAFA;border-left:solid 4px #E9E9E9;margin:1rem -1rem;min-width:70vw;padding:12px 1rem 12px calc(1rem - 4px);width:-moz-fit-content;width:-webkit-fit-content;width:fit-content;max-width:calc(100vw - 2rem);overflow-y:scroll}@media (min-width: 720px){pre{min-width:40ch}}.spec-example{background:#FAFAFF;border-left:solid 4px #BBBBFF;padding-top:8px}.spec-counter-example{background:#FFFAFA;border-left:solid 4px #FFBBBB;padding-top:8px}.spec-example>a,.spec-counter-example>a{display:block;font:italic 11pt/18pt Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;opacity:.6;user-select:none}.spec-counter-example>a{color:#98593b}table{border-collapse:collapse}th{background-color:#f9f9f9}td,th{border:1px solid #D0D0D0;padding:.4em;vertical-align:baseline}ol,ul{padding-left:var(--list-indent)}li>ol,li>ul{margin-top:.25em;margin-bottom:.5em}li+li{margin-top:.25em}li.task{list-style-type:none;position:relative}li.task>input:first-child{margin-left:0;position:absolute;transform:translateX(calc(-100% - 1ch))}ins{background-color:#00c81e14;text-decoration:none}del{background-color:#c8000014}.spec-added,.spec-removed{border-left:4px solid;margin-left:-18px;padding-left:14px}.spec-added{border-color:#396}.spec-removed{border-color:#933;text-decoration:line-through}.spec-keyword{font-weight:bold}.spec-string{font-family:Consolas,Monaco,monospace;font-size:85%;white-space:pre}var{font-style:italic}*[data-name]{transition:.15s background ease-out;border-radius:2px;padding:0 3px;margin:0 -3px}.spec-semantic,.spec-algo{margin:1rem 0 1rem var(--dfn-indent)}.spec-semantic>.spec-nt::after,.spec-algo>.spec-call:first-child::after{content:":";font-style:normal;font-weight:bold;margin-left:1ex}.spec-semantic ol,.spec-semantic ol ol ol ol,.spec-algo ol,.spec-algo ol ol ol ol{list-style-type:decimal}.spec-semantic ol ol,.spec-semantic ol ol ol ol ol,.spec-algo ol ol,.spec-algo ol ol ol ol ol{list-style-type:lower-alpha}.spec-semantic ol ol ol,.spec-semantic ol ol ol ol ol ol,.spec-algo ol ol ol,.spec-algo ol ol ol ol ol ol{list-style-type:lower-roman}.spec-call>a{color:inherit}.spec-production{margin:1rem 0 1rem var(--dfn-indent)}.spec-production>.spec-nt::after{content:":";font-style:normal;font-weight:bold;margin:0 1ex}.spec-semantic.d2>.spec-nt::after,.spec-production.d2>.spec-nt::after{content:"::"}.spec-semantic.d3>.spec-nt::after,.spec-production.d3>.spec-nt::after{content:":::"}.spec-production>.spec-rhs{line-height:1.1;margin:.25em 0 .5em calc(2 * var(--indent));text-indent:calc(-1 * var(--indent))}.spec-semantic>.spec-rhs{display:inline-block;text-indent:calc(-1 * var(--indent));margin-left:calc(1ex + var(--indent))}.spec-rhs>*{text-indent:0}.spec-oneof{display:inline}.spec-oneof::before{content:"one of";font-style:normal;font-weight:bold}.spec-oneof-grid{max-width:calc(100vw - 2rem);overflow:scroll;margin:-1ex -1rem;padding:1ex 1rem}.spec-oneof-grid>table{margin-left:var(--indent)}.spec-oneof .spec-rhs{border:none;margin:0;padding:0 0 0 1rem;vertical-align:baseline;white-space:pre}.spec-oneof .spec-rhs:first-child{padding:0}.spec-rhs .spec-constrained:not(:first-child),.spec-rhs .spec-quantified:not(:first-child),.spec-rhs .spec-nt:not(:first-child),.spec-rhs .spec-t:not(:first-child),.spec-rhs .spec-rx:not(:first-child),.spec-rhs .spec-prose:not(:first-child),.spec-rhs .spec-empty:not(:first-child),.spec-rhs .spec-lookahead:not(:first-child){margin-left:1ex;display:inline-block}.spec-condition{font-size:85%}.spec-condition::before{content:"[if "}.spec-condition.not::before{content:"[if not "}.spec-condition::after{content:"]"}.spec-empty,.spec-prose{color:#666}.spec-nt{font-style:italic}.spec-nt>a{color:inherit}.spec-quantifiers,.spec-params{font-size:65%;font-style:normal;vertical-align:sub}.spec-quantifier.list{color:#3348d3}.spec-quantifier.optional{color:#83238e}.spec-params,.spec-condition{color:#1c7758}.spec-params::before{content:"["}.spec-params::after{content:"]"}.spec-quantifier:not(:last-child)::after,.spec-param:not(:last-child)::after{color:#666;content:", "}.spec-param.conditional::before{content:"?"}.spec-param.negated::before{content:"!"}.spec-t,.spec-rx{color:#333;font-family:monospace;font-weight:bold}.spec-butnot::before{color:#666;content:"but not";font-family:Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;font-weight:normal;margin-right:1ex}.spec-butnot>*:not(:first-child)::before{color:#666;content:"or";font-family:Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;font-weight:normal;margin-right:1ex}.spec-rhs .spec-oneof::before,.spec-rhs .spec-butnot::before{margin-left:1ex}.spec-lookahead>*{margin:0!important}.spec-lookahead>*:not(:first-child)::before{color:#666;content:", ";font-family:Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;font-style:normal;font-weight:normal}.spec-lookahead::before{color:#666;content:"[lookahead = ";font-family:Cambria,"Palatino Linotype",Palatino,"Liberation Serif",serif;font-style:normal;font-weight:normal}.spec-lookahead.not::before{content:"[lookahead \2260 "}.spec-lookahead.set::before{content:"[lookahead \2208 {";margin-right:0}.spec-lookahead.set.not::before{content:"[lookahead \2209 {"}.spec-lookahead.ntset::before{content:"[lookahead \2208 ";margin-right:0}.spec-lookahead.ntset.not::before{content:"[lookahead \2209 "}.spec-lookahead::after{color:#666;content:"]"}.spec-lookahead.set::after{content:"}]"}.token.atrule,.token.attr-value,.token.keyword,.token.property,.token.selector,.token.attr-name,.token.builtin,.token.entity,.token.url,.token.inserted{color:#1b1994b0;background:none}.token.tag,.token.boolean,.token.number,.token.string,.token.char,.token.constant,.token.symbol,.token.regex,.token.important,.token.variable,.token.function,.token.class-name,.token.deleted{color:#a20764bd}.token.comment,.token.prolog,.token.doctype,.token.cdata,.token.description{color:inherit;opacity:.3}.token.punctuation{color:inherit;opacity:.5}.token.operator,.token.namespace{color:inherit;opacity:.7}</style>
<script>(function(){var r,a=[];document.addEventListener("readystatechange",function(){document.readyState==="interactive"&&u()});function u(){var n=document.querySelector('label[for="spec-sidebar-toggle"]');n.addEventListener("scroll",o),n.addEventListener("touchmove",o);function o(d){d.preventDefault()}for(var t=document.getElementsByTagName("section"),e=0;e<t.length;e++)t[e].getAttribute("secid")&&a.push(t[e]);var i=window.scrollY,c=!1;window.addEventListener("scroll",function(d){i=window.scrollY,c||(c=!0,window.requestAnimationFrame(function(){s(i),c=!1}))})}function s(n){for(var o=n+document.documentElement.clientHeight/4,t,e=a.length-1;e>=0;e--)if(a[e].offsetTop<o){t=a[e];break}var i=t&&t.getAttribute("secid");i!==r&&(r&&l(r,!1),i&&l(i,!0),r=i)}function l(n,o){document.getElementById("_sidebar_"+n).className=o?"viewing":"";for(var t=n.split(".");t.length;){var e=document.getElementById("_toggle_"+t.join("."));e&&(e.checked=o),t.pop()}}s(window.scrollY);})();</script>
<script>(function(){var n=document.getElementsByTagName("style")[0].sheet,e;function u(){e&&(n.deleteRule(e),e=void 0)}function d(t){u(),e=n.insertRule('*[data-name="'+t+'"] { background: rgba(230,215,0,0.12); }',n.cssRules.length)}document.documentElement.addEventListener("mouseover",function(t){var a=t.target.attributes["data-name"];a&&d(a.value)});document.documentElement.addEventListener("mouseout",u);})();</script>
<script>(function(){var R="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",o,p,r,g;document.addEventListener("selectionchange",E);window.addEventListener("resize",C);window.addEventListener("hashchange",x);window.addEventListener("load",x);function S(n){y(new URL(n.target.href))}function x(){y(window.location)}function y(n){var e=n.hash.match(/^#sel-([A-Za-z0-9-_]+)$/);if(!!e){g=e[1],r=k(g);var t=r.getBoundingClientRect(),d=Math.max(20,Math.floor((window.innerHeight-t.height)*.4));window.scrollTo(0,window.scrollY+t.y-d);var c=document.getSelection();c.empty(),c.addRange(r),C()}}function E(n){var e=document.getSelection();if(e.isCollapsed)o&&(o.parentNode.removeChild(o),o=null);else{var t=e.getRangeAt(0);(!r||t.compareBoundaryPoints(Range.START_TO_START,r)!==0||t.compareBoundaryPoints(Range.END_TO_END,r)!==0)&&(r=t,g=B(r),C())}}function C(){if(!!r){p||(p=document.getElementsByTagName("article")[0]),o||(o=document.createElement("a"),document.body.appendChild(o)),o.href="#sel-"+g,o.onclick=S,o.className=r.isOutdated?"outdated-selection-link":"selection-link",o.innerText=r.isOutdated?"!":"\u201F";var n=window.innerWidth<720,e=r.getBoundingClientRect();if(n)o.style.left=Math.floor(e.x+e.width/2+window.scrollX-13)+"px",o.style.top=Math.floor(e.bottom+window.scrollY+10)+"px";else{var t=p.getBoundingClientRect().x;o.style.left=Math.floor(t+window.scrollX-37)+"px",o.style.top=Math.floor(e.y+window.scrollY-3)+"px"}}}function B(n){var e="",t=N(n.startContainer),d=N(n.endContainer),c=M(t,d);return l(c),l(t.slice(c.length).concat(n.startOffset)),l(d.slice(c.length).concat(n.endOffset)),i(L(n.toString())),e;function i(a){do e+=R[a&31|(a>31?32:0)],a>>=5;while(a>0)}function l(a){i(a.length);for(var h=0;h<a.length;h++)i(a[h])}}function k(n){for(var e=new Array(64),t=0;t<64;t++)e[R.charCodeAt(t)]=t;var d=0,c=m(),i=m(),l=m(),a=w(),h=i.pop(),P=O(c.concat(i)),T=l.pop(),A=O(c.concat(l)),u=document.createRange();return u.setStart(P,h),u.setEnd(A,T),u.isOutdated=a!==void 0&&a!==L(u.toString()),u;function w(){for(var s=0,v=0;d<n.length;){var f=e[n.charCodeAt(d++)];if(s|=(f&31)<<v,v+=5,f<32)return s}}function m(){var s=w();if(s!=null){for(var v=new Array(s),f=0;f<s;f++)v[f]=w();return v}}}function N(n){for(var e=[];n!=document.body;){var t=n.parentNode;e.push(Array.prototype.indexOf.call(t.childNodes,n)),n=t}return e.reverse()}function O(n){for(var e=document.body,t=0;t<n.length&&e;t++)e=e.childNodes[n[t]];return e}function M(n,e){for(var t=0;t<n.length&&t<e.length&&n[t]===e[t];)t++;return n.slice(0,t)}function L(n){for(var e=2166136261,t=0;t<n.length;++t)e^=n.charCodeAt(t),e+=(e<<1)+(e<<4)+(e<<7)+(e<<8)+(e<<24);return(e>>15^e)&32767}})();</script>
</head>
<body><article>
<header>
<h1>GraphQL Cursor Connections Specification</h1>
<section id="intro">
<p>This specification aims to provide an option for GraphQL clients to consistently handle <a href="https://graphql.org/learn/pagination/">pagination best practices</a> with support for related metadata via a GraphQL server. This spec proposes calling this pattern “Connections” and exposing them in a standardized way.</p>
<p>In the query, the connection model provides a standard mechanism for slicing and paginating the result set.</p>
<p>In the response, the connection model provides a standard way of providing cursors, and a way of telling the client when more results are available.</p>
<p>An example of all four of those is the following query:</p>
<pre data-language="graphql"><code><span class="token punctuation">{</span>
user <span class="token punctuation">{</span>
id
name
friends<span class="token punctuation">(</span><span class="token attr-name">first</span><span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token attr-name">after</span><span class="token punctuation">:</span> <span class="token string">"opaqueCursor"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
edges <span class="token punctuation">{</span>
cursor
node <span class="token punctuation">{</span>
id
name
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
pageInfo <span class="token punctuation">{</span>
hasNextPage
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>In this case, <code>friends</code> is a connection. That query demonstrates the four features described above:</p>
<ul>
<li>Slicing is done with the <code>first</code> argument to <code>friends</code>. This asks for the connection to return 10 friends.</li>
<li>Pagination is done with the <code>after</code> argument to <code>friends</code>. We passed in a cursor, so we asked for the server to return friends after that cursor.</li>
<li>For each edge in the connection, we asked for a cursor. This cursor is an opaque string, and is precisely what we would pass to the <code>after</code> arg to paginate starting after this edge.</li>
<li>We asked for <code>hasNextPage</code>; that will tell us if there are more edges available, or if we’ve reached the end of this connection.</li>
</ul>
<p>This section of the spec describes the formal requirements around connections.</p>
</section>
<nav class="spec-toc">
<div class="title">Contents</div>
<ol>
<li><a href="#sec-Reserved-Types"><span class="spec-secid">1</span>Reserved Types</a></li>
<li><a href="#sec-Connection-Types"><span class="spec-secid">2</span>Connection Types</a><ol>
<li><a href="#sec-Connection-Types.Fields"><span class="spec-secid">2.1</span>Fields</a><ol>
<li><a href="#sec-Edges"><span class="spec-secid">2.1.1</span>Edges</a></li>
<li><a href="#sec-Connection-Types.Fields.PageInfo"><span class="spec-secid">2.1.2</span>PageInfo</a></li>
</ol>
</li>
<li><a href="#sec-Connection-Types.Introspection"><span class="spec-secid">2.2</span>Introspection</a></li>
</ol>
</li>
<li><a href="#sec-Edge-Types"><span class="spec-secid">3</span>Edge Types</a><ol>
<li><a href="#sec-Edge-Types.Fields"><span class="spec-secid">3.1</span>Fields</a><ol>
<li><a href="#sec-Node"><span class="spec-secid">3.1.1</span>Node</a></li>
<li><a href="#sec-Cursor"><span class="spec-secid">3.1.2</span>Cursor</a></li>
</ol>
</li>
<li><a href="#sec-Edge-Types.Introspection"><span class="spec-secid">3.2</span>Introspection</a></li>
</ol>
</li>
<li><a href="#sec-Arguments"><span class="spec-secid">4</span>Arguments</a><ol>
<li><a href="#sec-Forward-pagination-arguments"><span class="spec-secid">4.1</span>Forward pagination arguments</a></li>
<li><a href="#sec-Backward-pagination-arguments"><span class="spec-secid">4.2</span>Backward pagination arguments</a></li>
<li><a href="#sec-Edge-order"><span class="spec-secid">4.3</span>Edge order</a></li>
<li><a href="#sec-Pagination-algorithm"><span class="spec-secid">4.4</span>Pagination algorithm</a></li>
</ol>
</li>
<li><a href="#sec-undefined.PageInfo"><span class="spec-secid">5</span>PageInfo</a><ol>
<li><a href="#sec-undefined.PageInfo.Fields"><span class="spec-secid">5.1</span>Fields</a></li>
<li><a href="#sec-undefined.PageInfo.Introspection"><span class="spec-secid">5.2</span>Introspection</a></li>
</ol>
</li>
<li><a href="#index"><span class="spec-secid">§</span>Index</a></li>
</ol>
</nav>
</header>
<section id="sec-Reserved-Types" secid="1">
<h1><span class="spec-secid" title="link to this section"><a href="#sec-Reserved-Types">1</a></span>Reserved Types</h1>
<p>A GraphQL server which conforms to this spec must reserve certain types and type names to support the pagination model of connections. In particular, this spec creates guidelines for the following types:</p>
<ul>
<li>Any object whose name ends in “Connection”.</li>
<li>An object named <code>PageInfo</code>.</li>
</ul>
</section>
<section id="sec-Connection-Types" secid="2">
<h1><span class="spec-secid" title="link to this section"><a href="#sec-Connection-Types">2</a></span>Connection Types</h1>
<p>Any type whose name ends in “Connection” is considered by this spec to be a <em>Connection Type</em>. Connection types must be an “Object” as defined in the “Type System” section of the GraphQL Specification.</p>
<section id="sec-Connection-Types.Fields" secid="2.1">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Connection-Types.Fields">2.1</a></span>Fields</h2>
<p>Connection types must have fields named <code>edges</code> and <code>pageInfo</code>. They may have additional fields related to the connection, as the schema designer sees fit.</p>
<section id="sec-Edges" secid="2.1.1">
<h3><span class="spec-secid" title="link to this section"><a href="#sec-Edges">2.1.1</a></span>Edges</h3>
<p>A “Connection Type” must contain a field called <code>edges</code>. This field must return a list type that wraps an edge type, where the requirements of an edge type are defined in the “Edge Types” section below.</p>
</section>
<section id="sec-Connection-Types.Fields.PageInfo" secid="2.1.2">
<h3><span class="spec-secid" title="link to this section"><a href="#sec-Connection-Types.Fields.PageInfo">2.1.2</a></span>PageInfo</h3>
<p>A “Connection Type” must contain a field called <code>pageInfo</code>. This field must return a non-null <code>PageInfo</code> object, as defined in the “PageInfo” section below.</p>
</section>
</section>
<section id="sec-Connection-Types.Introspection" secid="2.2">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Connection-Types.Introspection">2.2</a></span>Introspection</h2>
<p>If <code>ExampleConnection</code> existed in the type system, it would be a connection, since its name ends in “Connection”. If this connection’s edge type was named <code>ExampleEdge</code>, then a server that correctly implements the above requirement would accept the following introspection query, and return the provided response:</p>
<pre data-language="graphql"><code><span class="token punctuation">{</span>
__type<span class="token punctuation">(</span><span class="token attr-name">name</span><span class="token punctuation">:</span> <span class="token string">"ExampleConnection"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fields <span class="token punctuation">{</span>
name
<span class="token keyword">type</span> <span class="token punctuation">{</span>
name
kind
ofType <span class="token punctuation">{</span>
name
kind
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>returns</p>
<pre data-language="json"><code><span class="token punctuation">{</span>
<span class="token property">"data"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"__type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"fields"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
<span class="token comment">// May contain other items</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"pageInfo"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"NON_NULL"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"PageInfo"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"OBJECT"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"edges"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"LIST"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"ExampleEdge"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"OBJECT"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</section>
</section>
<section id="sec-Edge-Types" secid="3">
<h1><span class="spec-secid" title="link to this section"><a href="#sec-Edge-Types">3</a></span>Edge Types</h1>
<p>A type that is returned in list form by a connection type’s <code>edges</code> field is considered by this spec to be an <em>Edge Type</em>. Edge types must be an “Object” as defined in the “Type System” section of the GraphQL Specification.</p>
<section id="sec-Edge-Types.Fields" secid="3.1">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Edge-Types.Fields">3.1</a></span>Fields</h2>
<p>Edge types must have fields named <code>node</code> and <code>cursor</code>. They may have additional fields related to the edge, as the schema designer sees fit.</p>
<section id="sec-Node" secid="3.1.1">
<h3><span class="spec-secid" title="link to this section"><a href="#sec-Node">3.1.1</a></span>Node</h3>
<p>An “Edge Type” must contain a field called <code>node</code>. This field must return either a Scalar, Enum, Object, Interface, Union, or a Non-Null wrapper around one of those types. Notably, this field <em>cannot</em> return a list.</p>
<div id="note-e0232" class="spec-note">
<a href="#note-e0232">Note</a>
The naming echoes that of the “Node” interface and “node” root field as described in a later section of this spec. Spec-compliant clients can perform certain optimizations if this field returns an object that implements <code>Node</code>, however, this is not a strict requirement for conforming.</div>
</section>
<section id="sec-Cursor" secid="3.1.2">
<h3><span class="spec-secid" title="link to this section"><a href="#sec-Cursor">3.1.2</a></span>Cursor</h3>
<p>An “Edge Type” must contain a field called <code>cursor</code>. This field must return a type that serializes as a String; this may be a String, a Non-Null wrapper around a String, a custom scalar that serializes as a String, or a Non-Null wrapper around a custom scalar that serializes as a String.</p>
<p>Whatever type this field returns will be referred to as the <em>cursor type</em> in the rest of this spec.</p>
<p>The result of this field should be considered opaque by the client, but will be passed back to the server as described in the “Arguments” section below.</p>
</section>
</section>
<section id="sec-Edge-Types.Introspection" secid="3.2">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Edge-Types.Introspection">3.2</a></span>Introspection</h2>
<p>If <code>ExampleEdge</code> is an edge type in our schema, that returned “Example” objects, then a server that correctly implements the above requirement would accept the following introspection query, and return the provided response:</p>
<pre data-language="graphql"><code><span class="token punctuation">{</span>
__type<span class="token punctuation">(</span><span class="token attr-name">name</span><span class="token punctuation">:</span> <span class="token string">"ExampleEdge"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fields <span class="token punctuation">{</span>
name
<span class="token keyword">type</span> <span class="token punctuation">{</span>
name
kind
ofType <span class="token punctuation">{</span>
name
kind
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>returns</p>
<pre data-language="json"><code><span class="token punctuation">{</span>
<span class="token property">"data"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"__type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"fields"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
<span class="token comment">// May contain other items</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"node"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Example"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"OBJECT"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token null keyword">null</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"cursor"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token comment">// This shows the cursor type as String!, other types are possible</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"NON_NULL"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"String"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"SCALAR"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</section>
</section>
<section id="sec-Arguments" secid="4">
<h1><span class="spec-secid" title="link to this section"><a href="#sec-Arguments">4</a></span>Arguments</h1>
<p>A field that returns a <em>Connection Type</em> must include forward pagination arguments, backward pagination arguments, or both. These pagination arguments allow the client to slice the set of edges before it is returned.</p>
<section id="sec-Forward-pagination-arguments" secid="4.1">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Forward-pagination-arguments">4.1</a></span>Forward pagination arguments</h2>
<p>To enable forward pagination, two arguments are required.</p>
<ul>
<li><code>first</code> takes a non-negative integer.</li>
<li><code>after</code> takes the <em>cursor type</em> as described in the <code>cursor</code> field section.</li>
</ul>
<p>The server should use those two arguments to modify the edges returned by the connection, returning edges after the <code>after</code> cursor, and returning at most <code>first</code> edges.</p>
<p>You should generally pass the <code>cursor</code> of the last edge in the previous page for <code>after</code>.</p>
</section>
<section id="sec-Backward-pagination-arguments" secid="4.2">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Backward-pagination-arguments">4.2</a></span>Backward pagination arguments</h2>
<p>To enable backward pagination, two arguments are required.</p>
<ul>
<li><code>last</code> takes a non-negative integer.</li>
<li><code>before</code> takes the <em>cursor type</em> as described in the <code>cursor</code> field section.</li>
</ul>
<p>The server should use those two arguments to modify the edges returned by the connection, returning edges before the <code>before</code> cursor, and returning at most <code>last</code> edges.</p>
<p>You should generally pass the <code>cursor</code> of the first edge in the next page for <code>before</code>.</p>
</section>
<section id="sec-Edge-order" secid="4.3">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Edge-order">4.3</a></span>Edge order</h2>
<p>You may order the edges however your business logic dictates, and may determine the ordering based upon additional arguments not covered by this specification. But the ordering must be consistent from page to page, and importantly, <em>The ordering of edges should be the same when using <code>first</code>/<code>after</code> as when using <code>last</code>/<code>before</code>, all other arguments being equal.</em> It should not be reversed when using <code>last</code>/<code>before</code>. More formally:</p>
<ul>
<li>When <code>before: cursor</code> is used, the edge closest to <code>cursor</code> must come <strong>last</strong> in the result <code>edges</code>.</li>
<li>When <code>after: cursor</code> is used, the edge closest to <code>cursor</code> must come <strong>first</strong> in the result <code>edges</code>.</li>
</ul>
</section>
<section id="sec-Pagination-algorithm" secid="4.4">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-Pagination-algorithm">4.4</a></span>Pagination algorithm</h2>
<p>To determine what edges to return, the connection evaluates the <code>before</code> and <code>after</code> cursors to filter the edges, then evaluates <code>first</code> to slice the edges, then <code>last</code> to slice the edges.</p>
<div id="note-a97ec" class="spec-note">
<a href="#note-a97ec">Note</a>
Including a value for both <code>first</code> and <code>last</code> is strongly discouraged, as it is likely to lead to confusing queries and results. The <code>PageInfo</code> section goes into more detail here.</div>
<p>More formally:</p>
<div class="spec-algo" id="EdgesToReturn()">
<span class="spec-call"><a href="#EdgesToReturn()" data-name="EdgesToReturn">EdgesToReturn</a>(<var data-name="allEdges">allEdges</var>, <var data-name="before">before</var>, <var data-name="after">after</var>, <var data-name="first">first</var>, <var data-name="last">last</var>)</span><ol>
<li>Let <var data-name="edges">edges</var> be the result of calling <span class="spec-call"><a href="#ApplyCursorsToEdges()" data-name="ApplyCursorsToEdges">ApplyCursorsToEdges</a>(<var data-name="allEdges">allEdges</var>, <var data-name="before">before</var>, <var data-name="after">after</var>)</span>.</li>
<li>If <var data-name="first">first</var> is set:<ol>
<li>If <var data-name="first">first</var> is less than 0:<ol>
<li>Throw an error.</li>
</ol>
</li>
<li>If <var data-name="edges">edges</var> has length greater than than <var data-name="first">first</var>:<ol>
<li>Slice <var data-name="edges">edges</var> to be of length <var data-name="first">first</var> by removing edges from the end of <var data-name="edges">edges</var>.</li>
</ol>
</li>
</ol>
</li>
<li>If <var data-name="last">last</var> is set:<ol>
<li>If <var data-name="last">last</var> is less than 0:<ol>
<li>Throw an error.</li>
</ol>
</li>
<li>If <var data-name="edges">edges</var> has length greater than than <var data-name="last">last</var>:<ol>
<li>Slice <var data-name="edges">edges</var> to be of length <var data-name="last">last</var> by removing edges from the start of <var data-name="edges">edges</var>.</li>
</ol>
</li>
</ol>
</li>
<li>Return <var data-name="edges">edges</var>.</li>
</ol>
</div>
<div class="spec-algo" id="ApplyCursorsToEdges()">
<span class="spec-call"><a href="#ApplyCursorsToEdges()" data-name="ApplyCursorsToEdges">ApplyCursorsToEdges</a>(<var data-name="allEdges">allEdges</var>, <var data-name="before">before</var>, <var data-name="after">after</var>)</span><ol>
<li>Initialize <var data-name="edges">edges</var> to be <var data-name="allEdges">allEdges</var>.</li>
<li>If <var data-name="after">after</var> is set:<ol>
<li>Let <var data-name="afterEdge">afterEdge</var> be the edge in <var data-name="edges">edges</var> whose <var data-name="cursor">cursor</var> is equal to the <var data-name="after">after</var> argument.</li>
<li>If <var data-name="afterEdge">afterEdge</var> exists:<ol>
<li>Remove all elements of <var data-name="edges">edges</var> before and including <var data-name="afterEdge">afterEdge</var>.</li>
</ol>
</li>
</ol>
</li>
<li>If <var data-name="before">before</var> is set:<ol>
<li>Let <var data-name="beforeEdge">beforeEdge</var> be the edge in <var data-name="edges">edges</var> whose <var data-name="cursor">cursor</var> is equal to the <var data-name="before">before</var> argument.</li>
<li>If <var data-name="beforeEdge">beforeEdge</var> exists:<ol>
<li>Remove all elements of <var data-name="edges">edges</var> after and including <var data-name="beforeEdge">beforeEdge</var>.</li>
</ol>
</li>
</ol>
</li>
<li>Return <var data-name="edges">edges</var>.</li>
</ol>
</div>
</section>
</section>
<section id="sec-undefined.PageInfo" secid="5">
<h1><span class="spec-secid" title="link to this section"><a href="#sec-undefined.PageInfo">5</a></span>PageInfo</h1>
<p>The server must provide a type called <code>PageInfo</code>.</p>
<section id="sec-undefined.PageInfo.Fields" secid="5.1">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-undefined.PageInfo.Fields">5.1</a></span>Fields</h2>
<p><code>PageInfo</code> must contain fields <code>hasPreviousPage</code> and <code>hasNextPage</code>, both of which return non-null booleans. It must also contain fields <code>startCursor</code> and <code>endCursor</code>, both of which return opaque strings. The fields <code>startCursor</code> and <code>endCursor</code> can be null if there are no results.</p>
<p><code>hasPreviousPage</code> is used to indicate whether more edges exist prior to the set defined by the clients arguments. If the client is paginating with <code>last</code>/<code>before</code>, then the server must return <span class="spec-keyword">true</span> if prior edges exist, otherwise <span class="spec-keyword">false</span>. If the client is paginating with <code>first</code>/<code>after</code>, then the client may return <span class="spec-keyword">true</span> if edges prior to <code>after</code> exist, if it can do so efficiently, otherwise may return <span class="spec-keyword">false</span>. More formally:</p>
<div class="spec-algo" id="HasPreviousPage()">
<span class="spec-call"><a href="#HasPreviousPage()" data-name="HasPreviousPage">HasPreviousPage</a>(<var data-name="allEdges">allEdges</var>, <var data-name="before">before</var>, <var data-name="after">after</var>, <var data-name="first">first</var>, <var data-name="last">last</var>)</span><ol>
<li>If <var data-name="last">last</var> is set:<ol>
<li>Let <var data-name="edges">edges</var> be the result of calling <span class="spec-call"><a href="#ApplyCursorsToEdges()" data-name="ApplyCursorsToEdges">ApplyCursorsToEdges</a>(<var data-name="allEdges">allEdges</var>, <var data-name="before">before</var>, <var data-name="after">after</var>)</span>.</li>
<li>If <var data-name="edges">edges</var> contains more than <var data-name="last">last</var> elements return <span class="spec-keyword">true</span>, otherwise <span class="spec-keyword">false</span>.</li>
</ol>
</li>
<li>If <var data-name="after">after</var> is set:<ol>
<li>If the server can efficiently determine that elements exist prior to <var data-name="after">after</var>, return <span class="spec-keyword">true</span>.</li>
</ol>
</li>
<li>Return <span class="spec-keyword">false</span>.</li>
</ol>
</div>
<p><code>hasNextPage</code> is used to indicate whether more edges exist following the set defined by the clients arguments. If the client is paginating with <code>first</code>/<code>after</code>, then the server must return <span class="spec-keyword">true</span> if further edges exist, otherwise <span class="spec-keyword">false</span>. If the client is paginating with <code>last</code>/<code>before</code>, then the client may return <span class="spec-keyword">true</span> if edges further from <code>before</code> exist, if it can do so efficiently, otherwise may return <span class="spec-keyword">false</span>. More formally:</p>
<div class="spec-algo" id="HasNextPage()">
<span class="spec-call"><a href="#HasNextPage()" data-name="HasNextPage">HasNextPage</a>(<var data-name="allEdges">allEdges</var>, <var data-name="before">before</var>, <var data-name="after">after</var>, <var data-name="first">first</var>, <var data-name="last">last</var>)</span><ol>
<li>If <var data-name="first">first</var> is set:<ol>
<li>Let <var data-name="edges">edges</var> be the result of calling <span class="spec-call"><a href="#ApplyCursorsToEdges()" data-name="ApplyCursorsToEdges">ApplyCursorsToEdges</a>(<var data-name="allEdges">allEdges</var>, <var data-name="before">before</var>, <var data-name="after">after</var>)</span>.</li>
<li>If <var data-name="edges">edges</var> contains more than <var data-name="first">first</var> elements return <span class="spec-keyword">true</span>, otherwise <span class="spec-keyword">false</span>.</li>
</ol>
</li>
<li>If <var data-name="before">before</var> is set:<ol>
<li>If the server can efficiently determine that elements exist following <var data-name="before">before</var>, return <span class="spec-keyword">true</span>.</li>
</ol>
</li>
<li>Return <span class="spec-keyword">false</span>.</li>
</ol>
</div>
<div id="note-95f8a" class="spec-note">
<a href="#note-95f8a">Note</a>
When both <code>first</code> and <code>last</code> are included, both of the fields should be set according to the above algorithms, but their meaning as it relates to pagination becomes unclear. This is among the reasons that pagination with both <code>first</code> and <code>last</code> is discouraged.</div>
<p><code>startCursor</code> and <code>endCursor</code> must be the cursors corresponding to the first and last nodes in <code>edges</code>, respectively.</p>
<div id="note-4ca64" class="spec-note">
<a href="#note-4ca64">Note</a>
As this spec was created with Relay Classic in mind, it’s worth noting that Relay Legacy did not define <code>startCursor</code> and <code>endCursor</code>, and relied on selecting the <code>cursor</code> of each edge; Relay Modern began selecting <code>startCursor</code> and <code>endCursor</code> instead to save bandwidth (since it doesn’t use any cursors in between).</div>
</section>
<section id="sec-undefined.PageInfo.Introspection" secid="5.2">
<h2><span class="spec-secid" title="link to this section"><a href="#sec-undefined.PageInfo.Introspection">5.2</a></span>Introspection</h2>
<p>A server that correctly implements the above requirement would accept the following introspection query, and return the provided response:</p>
<pre data-language="graphql"><code><span class="token punctuation">{</span>
__type<span class="token punctuation">(</span><span class="token attr-name">name</span><span class="token punctuation">:</span> <span class="token string">"PageInfo"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fields <span class="token punctuation">{</span>
name
<span class="token keyword">type</span> <span class="token punctuation">{</span>
name
kind
ofType <span class="token punctuation">{</span>
name
kind
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>returns</p>
<pre data-language="json"><code><span class="token punctuation">{</span>
<span class="token property">"data"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"__type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"fields"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
<span class="token comment">// May contain other fields.</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"hasNextPage"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"NON_NULL"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Boolean"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"SCALAR"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"hasPreviousPage"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"NON_NULL"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Boolean"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"SCALAR"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"startCursor"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"String"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"SCALAR"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token null keyword">null</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"endCursor"</span><span class="token punctuation">,</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"String"</span><span class="token punctuation">,</span>
<span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"SCALAR"</span><span class="token punctuation">,</span>
<span class="token property">"ofType"</span><span class="token operator">:</span> <span class="token null keyword">null</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</section>
</section>
<section id="index" secid="index" class="spec-index">
<h1>
<span class="spec-secid" title="link to the index"><a href="#index">§</a></span>Index</h1>
<ol>
<li><a href="#ApplyCursorsToEdges()">ApplyCursorsToEdges</a></li>
<li><a href="#EdgesToReturn()">EdgesToReturn</a></li>
<li><a href="#HasNextPage()">HasNextPage</a></li>
<li><a href="#HasPreviousPage()">HasPreviousPage</a></li>
</ol>
</section>
</article>
<footer>
Written in <a href="https://spec-md.com" target="_blank">Spec Markdown</a>.</footer>
<input hidden="" class="spec-sidebar-toggle" type="checkbox" id="spec-sidebar-toggle" aria-hidden=""><label for="spec-sidebar-toggle" aria-hidden=""><div class="spec-sidebar-button">☰</div></label>
<div class="spec-sidebar" aria-hidden="">
<div class="spec-toc">
<div class="title"><a href="#">GraphQL Cursor Connections Specification</a></div>
<ol><li id="_sidebar_1"><a href="#sec-Reserved-Types"><span class="spec-secid">1</span>Reserved Types</a></li>
<li id="_sidebar_2"><a href="#sec-Connection-Types"><span class="spec-secid">2</span>Connection Types</a>
<input hidden="" class="toggle" type="checkbox" id="_toggle_2"><label for="_toggle_2"></label>
<ol>
<li id="_sidebar_2.1"><a href="#sec-Connection-Types.Fields"><span class="spec-secid">2.1</span>Fields</a>
<input hidden="" class="toggle" type="checkbox" id="_toggle_2.1"><label for="_toggle_2.1"></label>
<ol>
<li id="_sidebar_2.1.1"><a href="#sec-Edges"><span class="spec-secid">2.1.1</span>Edges</a></li>
<li id="_sidebar_2.1.2"><a href="#sec-Connection-Types.Fields.PageInfo"><span class="spec-secid">2.1.2</span>PageInfo</a></li>
</ol>
</li>
<li id="_sidebar_2.2"><a href="#sec-Connection-Types.Introspection"><span class="spec-secid">2.2</span>Introspection</a></li>
</ol>
</li>
<li id="_sidebar_3"><a href="#sec-Edge-Types"><span class="spec-secid">3</span>Edge Types</a>
<input hidden="" class="toggle" type="checkbox" id="_toggle_3"><label for="_toggle_3"></label>
<ol>
<li id="_sidebar_3.1"><a href="#sec-Edge-Types.Fields"><span class="spec-secid">3.1</span>Fields</a>
<input hidden="" class="toggle" type="checkbox" id="_toggle_3.1"><label for="_toggle_3.1"></label>
<ol>
<li id="_sidebar_3.1.1"><a href="#sec-Node"><span class="spec-secid">3.1.1</span>Node</a></li>
<li id="_sidebar_3.1.2"><a href="#sec-Cursor"><span class="spec-secid">3.1.2</span>Cursor</a></li>
</ol>
</li>
<li id="_sidebar_3.2"><a href="#sec-Edge-Types.Introspection"><span class="spec-secid">3.2</span>Introspection</a></li>
</ol>
</li>
<li id="_sidebar_4"><a href="#sec-Arguments"><span class="spec-secid">4</span>Arguments</a>
<input hidden="" class="toggle" type="checkbox" id="_toggle_4"><label for="_toggle_4"></label>
<ol>
<li id="_sidebar_4.1"><a href="#sec-Forward-pagination-arguments"><span class="spec-secid">4.1</span>Forward pagination arguments</a></li>
<li id="_sidebar_4.2"><a href="#sec-Backward-pagination-arguments"><span class="spec-secid">4.2</span>Backward pagination arguments</a></li>
<li id="_sidebar_4.3"><a href="#sec-Edge-order"><span class="spec-secid">4.3</span>Edge order</a></li>
<li id="_sidebar_4.4"><a href="#sec-Pagination-algorithm"><span class="spec-secid">4.4</span>Pagination algorithm</a></li>
</ol>
</li>
<li id="_sidebar_5"><a href="#sec-undefined.PageInfo"><span class="spec-secid">5</span>PageInfo</a>
<input hidden="" class="toggle" type="checkbox" id="_toggle_5"><label for="_toggle_5"></label>
<ol>
<li id="_sidebar_5.1"><a href="#sec-undefined.PageInfo.Fields"><span class="spec-secid">5.1</span>Fields</a></li>
<li id="_sidebar_5.2"><a href="#sec-undefined.PageInfo.Introspection"><span class="spec-secid">5.2</span>Introspection</a></li>
</ol>
</li>
<li id="_sidebar_index"><a href="#index"><span class="spec-secid">§</span>Index</a></li>
</ol>
</div>
</div>
</body></html>