SharePointVault ~/gotchas/rest-api ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdM↓ list-item-versions-are-newest-first-and-include-current.md
sharepoint-vault›gotchas›rest-api›list-item-versions-are-newest-first-and-include-current.mdhistoryedit on GitHub
● GOTCHArest-api· 3 min read

List item versions come back newest-first and include the current one

applies to: SharePoint Onlinereviewed: 2026-11-10#rest-api#lists#versioning#dates#diagnostics

The version history of a list item (items(id)/versions) behaves the opposite way to the history of a file (/Versions): it is sorted newest first, and its first row is the current version (IsCurrentVersion: true). Code written against one shape silently mislabels the other. And on each item version, Created is UTC without the trailing Z – new Date() reads it as local time and shifts every timestamp by the browser's offset. Use Modified, which carries the Z.

Historie verzí položky seznamu (items(id)/versions) se chová opačně než historie souboru (/Versions): je seřazená od nejnovější a první řádek je aktuální verze (IsCurrentVersion: true). Kód psaný pro jeden tvar ten druhý tiše popíše špatně. A Created u verze položky je UTC bez koncového Z – new Date() ho přečte jako místní čas a každý čas posune o posun prohlížeče. Ber Modified, ten Z má.

Symptom

A "text history" panel built on list item versions shows the newest edit labelled as the original, or shows the current text twice (once as "current", read from the item, and once as the first history row). Timestamps are one or two hours off – but only in the browser, never in the raw JSON.

Measured on SharePoint Online (2026-11-10)

An item with ten major versions (current 10.0), read with odata=nometadata:

GET /_api/web/GetList('/sites/team/Lists/Posts')/items(34)/versions
    ?$select=VersionId,VersionLabel,IsCurrentVersion,Modified,Created,Editor
Row VersionId VersionLabel IsCurrentVersion Modified Created
first 5120 10.0 true 2026-11-10T12:43:07Z 2026-11-10T12:43:07
second 4608 9.0 false 2026-11-10T12:37:18Z 2026-11-10T12:37:18
last 512 1.0 false 2026-10-29T19:54:23Z 2026-10-29T19:54:23
  • Descending, current included. The item itself reports _UIVersionString = 10.0 – the same as the first row. Compare with file versions, which are ascending and never contain the current version (file-versions-are-oldest-first).
  • $top keeps the newest end here. $top=2 returned 10.0, 9.0. A cap on item versions drops the oldest versions – the original text, which is often exactly what a "what did it say before review" feature needs. Put the cap above the list's MajorVersionLimit (read it from the list – the measured list had 500) or omit it.
  • Created has no Z. Same instant as Modified, but without the UTC designator. new Date('2026-11-10T12:43:07') is 12:43 local time – in Central Europe an hour off in winter, two in summer.
  • VersionId is major * 512 + minor (10.0 → 5120). It sorts correctly; VersionLabel sorted as a string puts 10.0 before 2.0.
  • Editor comes back as { LookupId, LookupValue, Email } without $expand; LookupValue is the display name.
  • Custom columns are returned by their internal name in $select (PostBody, not the display name) – same as on the item.

What to do

const url = `${webUrl}/_api/web/GetList('${listUrl}')/items(${id})/versions`
  + `?$select=VersionId,VersionLabel,IsCurrentVersion,Modified,Editor,Title,Body`;

const rows = (await get(url)).value || [];

// Do not trust the order – sort explicitly (works for both item and file shapes).
const oldestFirst = rows.slice().sort((a, b) => a.VersionId - b.VersionId);

const history = oldestFirst.map(v => ({
  label: v.VersionLabel,
  at: v.Modified,                       // has the Z; Created does not
  by: v.Editor && v.Editor.LookupValue,
  isCurrent: v.IsCurrentVersion === true
}));

Two more habits that keep a history UI honest:

  • Read without the browser cache right after your own write – a cached response can lack the version you just created (add a cache-busting query parameter or cache: 'no-store').
  • A failed read is not "no history". Show an error with retry instead of an empty history panel.

How to confirm it on your tenant

GET …/items(<id>)/versions?$select=VersionLabel,IsCurrentVersion,Modified,Created&$top=1
GET …/items(<id>)?$select=OData__UIVersionString

If the single row has IsCurrentVersion: true and the same label as OData__UIVersionString, you are reading the newest end. Compare Created and Modified on that row to see whether the Z is missing.

Related

Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last reviewed: 2026-11-10last updated: 2026-11-10historyedit on GitHubview source