GLAM Labs workshop, 24 June 2026
transforms the effectivities of socio-technical systems, making them work, or un-work, often in new and unexpected ways
— Mark Olson (2013), "Hacking the humanities: Twenty-first-century literacies and the ‘becoming other' of the humanities".
Hacking the systems that construct and control access to our cultural collections is at the core of humanities practice in the early 21st century. As online collections continue to expand, we need to carve out spaces that resist the weight of scale and foster alternative perspectives. As interfaces grow in sophistication and complexity, we need to stage playful and pointed interventions that reveal their limits and empower critique. We do not all have to be coders, but we do have to take code seriously. We have to take what we are given by collection databases and change it.
examine HTML code
check for errors
track what gets loaded
view styles
looks like metadata!
file url
{"beaconO22":"531",
"context" : "L",
"@id" : "https://eu01.alma.exlibrisgroup.com/primaws/rest/pub/pnxs/L/99116987653104341",
"adaptor" : "Local Search Engine",
"pnx" : {
"display" : {
"source" : [ "Alma" ],
"type" : [ "book" ],
"language" : [ "eng" ],
"title" : [ "\"Midland naturalist\" monthly metereological synopsis." ],
"format" : [ "1 sheet ([2] pages) ; 39 x 24 cm ; folded 24 x 20 cm" ],
"creationdate" : [ "1882" ],
"publisher" : [ "no publisher identified" ],
"mms" : [ "99116987653104341" ],
"contributor" : [ "Carr, E. Donald, Rev., inscriber. Library Holding: StEdNL$$QCarr, E. Donald", "Wragge, Clement L. (Clement Lindley), 1852-1922, former owner. Library Holding: StEdNL$$QWragge, Clement L." ],
"genre" : [ "Observations", "Sources" ],
"place" : [ "Birmingham? :" ],
"version" : [ "0" ],
"lds01" : [ "A pre-printed record, folio, folding so as to form a letter with the printed addressee details of “Clement L. Wragge ... Ben Nevis Observatory, Fort William, N.B.” The document was printed for completion by weather watchers from other parts of Britain, who provided meteorological observations for publication in \"The Midland Naturalist: The Journal of the Associated Natural History, Philosophical and Archaeology Societies and Field Clubs of the Midland Counties,\" which was published from 1878 to 1893. Wragge was a regular contributor to the journal.", "Clement Lindley Wragge (1852-1922), a British-born meteorologist, began his meteorological career in North Staffordshire. His work drew the attention of the Scottish Meteorological Society, which selected him to climb almost daily up to the summit of Ben Nevis to take meteorological observations. He won the Society’s Gold Medal for the continuous series of readings he made between 1 June and 14 October 1881. His wife, Leonora, made comparative observations at sea level at Fort William. In 1882 he had two assistants, William M. Whyte and Angus Rankin, who made the readings without him in 1883. During 1883 a special purpose-built observatory was constructed on the summit. The building was staffed full-time until 1904, when it was forced to close due to a lack of Government funding." ],
"lds02" : [ "The printed folio has been completed in ink by the Rev. E. Donald Carr, Woolstaston (Wolstaston), Shropshire, for March 1882, and posted to Clement Wragge, with Shrewsbury and Fort Willliam postmarks. The Rev. Carr has recorded details from the local meteorological station including rainfall, temperature, notes, and remarks about changes in equipment (“as before”). <br>Library Holding: StEdNL" ],
"lds18" : [ "Birmingham? " ],
"subject" : [ "Meteorology -- History -- 19th century$$QMeteorology -- History -- 19th century -- Observations Sources" ]
},
"control" : {
"sourcerecordid" : [ "99116987653104341" ],
"recordid" : [ "alma99116987653104341" ],
"sourceid" : "alma",
"originalsourceid" : [ "99116987653104341" ],
"sourcesystem" : [ "44NLS_INST" ],
"sourceformat" : [ "MARC21" ],
"score" : [ "1.0" ],
"isDedup" : false
},
"addata" : {
"originatingSystemIDContributor" : [ "no2002003249" ],
"aulast" : [ "Carr", "Wragge" ],
"aufirst" : [ "E. Donald", "Clement L." ],
"auinit" : [ "E", "C" ],
"addau" : [ "Carr, E. Donald", "Wragge, Clement L." ],
"contributorfull" : [ "$$NCarr, E. Donald$$LCarr$$FE. Donald$$Rcontributor", "$$NWragge, Clement L.$$Rcontributor" ],
"date" : [ "1882" ],
"cop" : [ "Birmingham?" ],
"pub" : [ "no publisher identified" ],
"format" : [ "book" ],
"genre" : [ "book" ],
"ristype" : [ "BOOK" ],
"btitle" : [ "\"Midland naturalist\" monthly metereological synopsis." ]
},
"sort" : {
"title" : [ "Midland naturalist\" monthly metereological synopsis." ],
"author" : [ "Carr, E. Donald, Rev., inscriber." ],
"creationdate" : [ "1882" ]
},
"facets" : {
"frbrtype" : [ "6" ]
}
},
"delivery" : {
"bestlocation" : {
"isValidUser" : true,
"organization" : "44NLS_INST",
"libraryCode" : "SCRR",
"availabilityStatus" : "available",
"subLocation" : "Special Collections Reading Room, Edinburgh (request to consult)",
"subLocationCode" : "GB06SC",
"mainLocation" : "Special Collections Reading Room",
"callNumber" : "AP.3.224.10",
"callNumberType" : "8",
"holdingURL" : "OVP",
"adaptorid" : "ALMA_01",
"ilsApiId" : "99116987653104341",
"holdId" : "22712530560004341",
"holKey" : "HoldingResultKey [mid=22712530560004341, libraryId=202601380004341, locationCode=GB06SC, callNumber=AP.3.224.10]",
"matchForHoldings" : [ {
"matchOn" : "MainLocation",
"holdingRecord" : "852##b"
} ],
"stackMapUrl" : "",
"relatedTitle" : null,
"translateRelatedTitle" : null,
"yearFilter" : null,
"volumeFilter" : null,
"singleUnavailableItemProcessType" : null,
"boundWith" : false,
"@id" : "_:0"
},
"holding" : [ {
"isValidUser" : true,
"organization" : "44NLS_INST",
"libraryCode" : "SCRR",
"availabilityStatus" : "available",
"subLocation" : "Special Collections Reading Room, Edinburgh (request to consult)",
"subLocationCode" : "GB06SC",
"mainLocation" : "Special Collections Reading Room",
"callNumber" : "AP.3.224.10",
"callNumberType" : "8",
"holdingURL" : "OVP",
"adaptorid" : "ALMA_01",
"ilsApiId" : "99116987653104341",
"holdId" : "22712530560004341",
"holKey" : "HoldingResultKey [mid=22712530560004341, libraryId=202601380004341, locationCode=GB06SC, callNumber=AP.3.224.10]",
"matchForHoldings" : [ {
"matchOn" : "MainLocation",
"holdingRecord" : "852##b"
} ],
"stackMapUrl" : "",
"relatedTitle" : null,
"translateRelatedTitle" : null,
"yearFilter" : null,
"volumeFilter" : null,
"singleUnavailableItemProcessType" : null,
"boundWith" : false,
"@id" : "_:0"
} ],
"electronicServices" : null,
"additionalElectronicServices" : null,
"filteredByGroupServices" : null,
"quickAccessService" : null,
"deliveryCategory" : [ "Alma-P" ],
"serviceMode" : [ "ovp" ],
"availability" : [ "available_in_library" ],
"availabilityLinks" : [ "detailsgetit1" ],
"availabilityLinksUrl" : [ ],
"displayedAvailability" : null,
"displayLocation" : true,
"additionalLocations" : false,
"titleRequestableAtItemLevel" : true,
"physicalItemTextCodes" : null,
"feDisplayOtherLocations" : false,
"almaInstitutionsList" : [ ],
"recordInstitutionCode" : null,
"recordOwner" : "44NLS_INST",
"hasFilteredServices" : null,
"digitalAuxiliaryMode" : false,
"digitalAuxiliaryThumbnail" : false,
"hideResourceSharing" : false,
"sharedDigitalCandidates" : null,
"consolidatedCoverage" : null,
"electronicContextObjectId" : null,
"almaOpenurl" : null,
"GetIt1" : [ {
"category" : "Alma-P",
"links" : [ {
"isLinktoOnline" : false,
"getItTabText" : "service_getit",
"adaptorid" : "ALMA_01",
"ilsApiId" : "99116987653104341",
"link" : "OVP",
"inst4opac" : "44NLS_INST",
"displayText" : null,
"@id" : "_:0"
} ]
} ],
"physicalServiceId" : null,
"link" : [ ],
"hasD" : null
},
"enrichment" : {
"virtualBrowseObject" : {
"isVirtualBrowseEnabled" : true,
"callNumber" : "AP.3.224.10",
"callNumberBrowseField" : "8"
}
}
}https://search.nls.uk/primaws/rest/pub/pnxs/L/alma99116987653104341?vid=44NLS_INST:44NLS_VU1&lang=en&search_scope=MainCatalogue&adaptor=Local%20Search%20Engine&lang=enitem id
if you know the item id you can get data!
all ways of running javascript in your browser to change the content or behaviour of websites
javascript:const nlsLinks=document.evaluate("//td[contains(., 'digital.nls.uk')]",document,null,XPathResult.ANY_TYPE,null),nlsLink=nlsLinks.iterateNext();window.open("https://commons.wikimedia.org/w/index.php?title=Special:LinkSearch&target="+nlsLink.textContent.replace("https://",""));
// Get digital item identifier
const nlsLinks = document.evaluate("//td[contains(., 'digital.nls.uk')]", document, null, XPathResult.ANY_TYPE, null);
const nlsLink = nlsLinks.iterateNext();
// Construct a search url, then open it
window.open("https://commons.wikimedia.org/w/index.php?title=Special:LinkSearch&target=" + nlsLink.textContent.replace("https://", ""));
spaces and linebreaks removed or replaced
tells the browser to run this as code
javascript:const nlsLinks=document.evaluate("//td[contains(., 'digital.nls.uk')]",document,null,XPathResult.ANY_TYPE,null),nlsLink=nlsLinks.iterateNext();window.open("https://commons.wikimedia.org/w/index.php?title=Special:LinkSearch&target="+nlsLink.textContent.replace("https://",""));
// Get digital item identifier
const nlsLinks = document.evaluate("//td[contains(., 'digital.nls.uk')]", document, null, XPathResult.ANY_TYPE, null);
const nlsLink = nlsLinks.iterateNext();
// Construct a search url, then open it
window.open("https://commons.wikimedia.org/w/index.php?title=Special:LinkSearch&target=" + nlsLink.textContent.replace("https://", ""));Tampermonkey is available as an extension or add on for most browsers, to install it:
Note that if you're using Tampermonkey with Chrome-based browsers (including Edge and Vivaldi), you have to explicitly give it permission to run userscripts after installation.
added by a userscript
// ==UserScript==
// @name NLS add Wikimedia links
// @namespace wraggelabs.com/nls-wiki-links
// @match *://digital.nls.uk/*
// @connect commons.wikimedia.org
// @grant GM_xmlhttpRequest
// @version 1.0
// @author Tim Sherratt (timsherratt.au)
// @description 23/06/2026, 10:37:18
// ==/UserScript==
(function() {
'use strict';
// Make a new row for the metadata table and insert the list of links
function makeRow(label, list) {
let tr = document.createElement("tr");
let th = document.createElement("th");
th.textContent = label;
let td = document.createElement("td");
td.appendChild(list);
tr.appendChild(th);
tr.appendChild(td);
return tr;
}
// Get the digital identifier
const nlsLinks = document.evaluate("//td[contains(., 'digital.nls.uk')]", document, null, XPathResult.ANY_TYPE, null);
const nlsLink = nlsLinks.iterateNext();
// Only proceed if there is a digital id on this page
if (nlsLink) {
console.log(nlsLink.textContent);
// Create a url to search for the pages with the id in Wikimedia Commons
const searchUrl = "https://commons.wikimedia.org/w/api.php?action=query&format=json&list=exturlusage&euquery=" + nlsLink.textContent.replace("https://", "");
const headers = {"User-Agent": "Userscript/NLS add Wikimedia Commons links (tim@timsherratt.au)"}
// Query the Wikimedia API for JSON results
GM_xmlhttpRequest({
method: "GET",
url: searchUrl,
responseType: "json",
headers: headers,
onload: function(response) {
console.log(response);
// Extract page ids from the results (if any)
let pageIds = [];
for (let page of response.response["query"]["exturlusage"]) {
pageIds.push(page["pageid"]);
}
// If there are results, we'll get more info using the page ids
if (pageIds.length > 0) {
// Create a url using the page ids to get more info
let infoUrl = "https://commons.wikimedia.org/w/api.php?action=query&pageids=" + pageIds.join("|") + "&prop=info|globalusage|iwlinks|categories&inprop=url&format=json";
console.log(infoUrl);
GM_xmlhttpRequest({
method: "GET",
url: infoUrl,
responseType: "json",
headers: headers,
onload: function(response) {
// The results contain info on Commons pages, other WM pages that
// use the image, links from the Commons page, and categories that
// the image is part of.
// We'll loop through each of these sets, creating arrays of HTML links.
let commonsList = document.createElement("ul");
let usageList = document.createElement("ul");
let linkList = document.createElement("ul");
let catList = document.createElement("ul");
// Commons pages
for (const [key, value] of Object.entries(response.response.query.pages)) {
let listItem = document.createElement("li");
listItem.innerHTML = "<a href='" + value.canonicalurl + "'>" + value.title + "</a>";
commonsList.appendChild(listItem);
// Usage on WP pages
for (let usage of value.globalusage) {
let usageItem = document.createElement("li");
usageItem.innerHTML = "<a href='" + usage.url + "'>" + usage.title + "</a> (" + usage.wiki + ")";
usageList.append(usageItem);
}
// Links from Commons page
for (let link of value.iwlinks) {
let linkItem = document.createElement("li");
linkItem.innerHTML = "<a href='" + link.url + "'>" + link["*"] + "</a> (" + link.prefix + ")";
linkList.append(linkItem);
}
// Commons categories
for (let cat of value.categories) {
let catItem = document.createElement("li");
catItem.innerHTML = "<a href='https://commons.wikimedia.org/wiki/" + cat.title + "'>" + cat.title + "</a>";
catList.append(catItem);
}
}
// Create a new table
let table = document.createElement("table");
table.className = "metadata";
let caption = document.createElement("caption");
caption.textContent = "Wikimedia Links";
table.appendChild(caption);
let tbody = document.createElement("tbody");
// Add new rows for each of the link lists
let commonsRow = makeRow("Commons pages", commonsList);
tbody.appendChild(commonsRow);
let usageRow = makeRow("Used on pages", usageList);
tbody.appendChild(usageRow);
let linkRow = makeRow("Related links", linkList);
tbody.appendChild(linkRow);
let catRow = makeRow("Commons categories", catList);
tbody.appendChild(catRow);
table.appendChild(tbody);
// Get the metdata section of the page
let metadataList = document.querySelector("div#maincontent");
// Add the new table to the end of the metadata section of the page
metadataList.appendChild(table);
}
});
}
}
});
}
})();To install:
click icon to open options
toggle userscript on/off
open dashboard
click on a script to edit it
// ==UserScript==
// @name My First Userscript
// @namespace http://tampermonkey.net/
// @version 2026-06-24
// @author You
// @match https://www.nls.uk/
// @icon https://www.google.com/s2/favicons?sz=64&domain=nls.uk
// @grant none
// ==/UserScript==
(function() {
'use strict';
const button = document.querySelector("a.page-header__cta");
button.textContent = "Explore our cool stuff!";
})();metadata
code
seeing people in the archives
browse collection images!
// ==UserScript==
// @name NLS add images to homepage
// @namespace wraggelabs.com/nls-home-thumbnails
// @version 2026-06-23
// @description Adds a random selection of thumbnail images to the NLS home page
// @author Tim Sherratt (timsherratt.au)
// @match https://www.nls.uk/
// @icon https://www.google.com/s2/favicons?sz=64&domain=nls.uk
// @grant GM_xmlhttpRequest
// @grant GM_addElement
// ==/UserScript==
(function() {
'use strict';
// CONFIGURATION SECTION
// Add/remove manifest urls
const manifests = [
"https://view.nls.uk/manifest/2438/7517/243875173/manifest.json",
"https://view.nls.uk/manifest/1897/4224/189742241/manifest.json",
"https://view.nls.uk/manifest/7549/75496599/manifest.json",
"https://view.nls.uk/manifest/7445/74457611/manifest.json"
];
// Max number of images to display
const numImages = 57;
// Size of thumbnails
const thumbSize = 50;
// END CONFIG SECTION
const header = document.querySelector("header.page-header > div");
const imageDiv = document.createElement("div");
imageDiv.style.display = "flex";
imageDiv.style.marginTop = "20px";
imageDiv.style.width = "100%";
imageDiv.style.flexWrap = "wrap";
const allImages = [];
var manifestsProcessed = 0;
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
for (let manifestUrl of manifests) {
GM_xmlhttpRequest({
method: "GET",
url: manifestUrl,
responseType: "json",
onload: function(response) {
var link, title;
const manifest = response.response;
for (let md of manifest.metadata) {
if (md.value.includes("digital.nls.uk")) {
link = md.value.match(/(https?:\/\/digital.nls.uk\/\d+)/)[0];
} else if (md.label == "Title") {
title = md.value;
}
}
for (let canvas of manifest.sequences[0].canvases) {
for (let img of canvas.images) {
let imageId = img.resource.service["@id"];
allImages.push({"src": `${imageId}/square/${thumbSize},/0/default.jpg`, "link": link, "title": title});
}
}
manifestsProcessed++;
displayImages();
}
});
}
function displayImages() {
if (manifests.length == manifestsProcessed) {
shuffleArray(allImages);
for (let image of allImages.slice(0, numImages)) {
let imageLink = document.createElement("a");
imageLink.href = image.link;
GM_addElement(imageLink, 'img', {
src: image.src,
title: image.title,
style: "margin: 4px"
});
imageDiv.appendChild(imageLink);
}
header.appendChild(imageDiv);
}
}
})();const manifests = [
"https://view.nls.uk/manifest/2438/7517/243875173/manifest.json",
"https://view.nls.uk/manifest/1897/4224/189742241/manifest.json",
"https://view.nls.uk/manifest/7549/75496599/manifest.json",
"https://view.nls.uk/manifest/7445/74457611/manifest.json"
];
watch out for commas and quotes!
// ==UserScript==
// @name NLS add catalogue info
// @namespace wraggelabs.com/nls-add-catalogue-info
// @match *://search.nls.uk/discovery*
// @grant none
// @version 1.0
// @author -
// @description 23/06/2026, 14:41:35
// @require https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.3/waitForKeyElements.js
// ==/UserScript==
function changeDisplay() {
if (document.location.href.includes("fulldisplay")) {
waitForKeyElements("#full-view-container", (container) => {
let details = document.querySelector("#item-details div.spaced-rows");
let newRow = document.createElement("div");
newRow.className="layout-block-xs layout-xs-column layout-row";
let newLabel = document.createElement("div");
newLabel.className = "flex-gt-xs-25 flex-gt-sm-20 flex bold-text";
newLabel.innerHTML = "New info";
let newValue = document.createElement("div");
newValue.className = "flex";
newValue.innerHTML = "Some additional piece of information";
newRow.appendChild(newLabel);
newRow.appendChild(newValue);
details.appendChild(newRow);
});
}
}
// This function looks for clicks in search results that show full item details in an overlay.
// These clicks don't actually open a new page, even though the url changes.
function watchHistoryEvents() {
const { pushState, replaceState } = window.history;
window.history.pushState = function (...args) {
pushState.apply(window.history, args);
window.dispatchEvent(new Event('pushState'));
};
window.addEventListener('pushState', changeDisplay);
}
watchHistoryEvents();
changeDisplay();