Image
Image
Find the Closest Show!
Postmodern Jukebox
Image

Find a Show Near You!

Image

North America

Image

On Tour

Image

North American Tix for the PMJ 2026 "The Future Is Vintage" World Tour are now available.

UK & Europe

Image

On Tour

Image

UK & Europe! Tix for the PMJ 2026 "The Future is Vintage" World Tour are now available

Aus & NZ

Image

On Tour

Image

Australia will be hosting Tix for the PMJ 2026 "The Future is Vintage" World Tour

No shows in your area?
Request your city!

Subscribe

LIVE ON TOUR • PARIS • LONDON • DUBLIN • LIVERPOOL • HELSINKI • PERTH • SYDNEY • BRUSSELS

LIVE ON TOUR • PARIS • LONDON • DUBLIN • LIVERPOOL • HELSINKI • PERTH • SYDNEY • BRUSSELS

LIVE ON TOUR • PARIS • LONDON • DUBLIN • LIVERPOOL • HELSINKI • PERTH • SYDNEY • BRUSSELS

LIVE ON TOUR • PARIS • LONDON • DUBLIN • LIVERPOOL • HELSINKI • PERTH • SYDNEY • BRUSSELS

Postmodern JukeboxPostmodern Jukebox merchandise
Image

Visit the
PMJ Shop

From vinyl LPs and physical CDs to retro-inspired tees and outerwear, our curated PMJ merch offerings allows you to build your own collection of timeless items. See our gift ideas for the Old Soul in your life, too!

Shop Coming Soon →
Postmodern Jukebox band members
Image

About the Band

Postmodern Jukebox

Image

When New York City arranger / pianist Scott Bradlee founded Postmodern Jukebox out of a basement in Queens in 2010, his goal was simple: to reimagine the pop hits of today as the timeless sounds of the music legends of yesteryear. Taylor Swift became Ella Fitzgerald. U2 became James Brown. The Spice Girls became The Andrews Sisters. Sabrina Carpenter became Karen Carpenter.

Now, over a decade and two billion views later, Postmodern Jukebox has grown to become a cultural mainstay in its own right, having played over a thousand shows on six continents worldwide — including acclaimed venues like Royal Albert Hall in London, Radio City Music Hall in New York City, and Sydney Opera House. In the process, PMJ has introduced its global audience of "Old Souls" to many exciting new singers, dancers, and instrumentalists – a number of whom have gone on to become celebrated recording artists and Broadway stars.

Often described as a "Saturday Night Live for singers," a Postmodern Jukebox show is a dazzling trip through 100 years of vintage popular music genres, brought to life by a cast of stars from its popular video channel. Whether as a solo listening experience, a well-deserved date night, or a celebration with family and friends, this all-ages, cross-generational celebration makes Pop Music History, each and every time.

). * API: POST /app/api/leads; optional POST /app/api/community-subscribe when `phone_full` is present * (runs before leads + Webflow). Community Invite/Update Member accepts `given_name`, `surname`, * `city`, and `email` per https://developer.community.com/reference/upsert-member — proxied as-is. * Form fields may still use Webflow names like `firstName` / “First Name”; the embed maps those to `given_name`/`surname` for `/community-subscribe` only (`/leads` keeps `firstName`/`lastName` as today). * * intl-tel-input: Webflow often uses `type="text"` + `.iti__tel-input` (not `type="tel"`). We resolve the * plugin element via `[data-intl-tel-input-id]`, `.iti__tel-input`, or `.iti` + text input, then * `intlTelInput.getInstance(input)` → `await iti.promise` → `isValidNumber()` / `getNumber()` for E.164. * Do not rely on a `phone_full` hidden field being populated before submit. * * **Country code in Webflow / intl-tel-input (site script, not this file):** use plugin options such as * `initialCountry: "us"` (or `"auto"` with `geoIpLookup`), `nationalMode: false` for full international * format, and `strictMode: true` if you want stricter typing. This embed blocks submit when the ITI field * has text but `isValidNumber()` is false (incomplete / wrong country). */ (function () { var LEADS_URL = "/app/api/leads"; var COMMUNITY_URL = "/app/api/community-subscribe"; var LOG = "[form-leads]"; /** Set on the form while re-dispatching so we don’t intercept the Webflow submit. */ var SKIP_FLAG = "__formLeadsSkipIntercept"; /** Console-safe hint for phone values (last 4 digits only). */ function maskPhoneForLog(value) { if (value == null || String(value).trim() === "") return "(empty)"; var d = String(value).replace(/\D/g, ""); if (d.length <= 4) return "(short)"; return "…" + d.slice(-4); } /** Original submit label while showing data-wait / Please wait... */ var SUBMIT_LABEL_BACKUP = "__formLeadsSubmitLabelBackup"; function log() { var a = [LOG]; for (var i = 0; i < arguments.length; i++) a.push(arguments[i]); console.log.apply(console, a); } function warn() { var a = [LOG]; for (var i = 0; i < arguments.length; i++) a.push(arguments[i]); console.warn.apply(console, a); } function getListSource(form) { if (!(form instanceof HTMLFormElement)) return null; if (form.hasAttribute("data-listid")) return form; return form.closest("[data-listid]"); } function getSubscribeCheckbox(form) { if (!(form instanceof HTMLFormElement)) return null; return form.querySelector('input[type="checkbox"][data-subscribe]'); } function getFormWrapper(form) { return form.closest(".w-form") || form.parentElement; } function getMessageElements(form) { var wrap = getFormWrapper(form); if (!wrap) return { success: null, fail: null }; return { success: wrap.querySelector(".w-form-done, .wf-form-done"), fail: wrap.querySelector(".w-form-fail, .wf-form-fail"), }; } function pickField(fd, names) { var i; var k; var v; for (i = 0; i < names.length; i++) { v = fd.get(names[i]); if (v != null && String(v).trim()) return String(v).trim(); } for (var pair of fd.entries()) { k = pair[0]; v = pair[1]; if ( v != null && String(v).trim() && names.some(function (n) { return n.toLowerCase() === k.toLowerCase(); }) ) { return String(v).trim(); } } return ""; } /** Webflow inputs often use `data-name="First Name"` while `name` is generic; match label loosely. */ function findInputValueByDataName(form, re) { if (!(form instanceof HTMLFormElement)) return ""; var els = form.querySelectorAll("[data-name]"); var i; var el; var dn; var v; for (i = 0; i < els.length; i++) { el = els[i]; dn = el.getAttribute("data-name"); if (!dn || !re.test(String(dn).trim())) continue; if ("value" in el && el.value != null) { v = String(el.value).trim(); if (v) return v; } } return ""; } function findEmail(form, fd) { var el = form.querySelector( 'input[type="email"], input[name="email"], input[name="Email"]' ); if (el && el.value && String(el.value).trim()) return String(el.value).trim(); var v = fd.get("email") || fd.get("Email") || fd.get("EMAIL"); if (v && String(v).trim()) return String(v).trim(); for (var pair of fd.entries()) { if (/email/i.test(pair[0]) && pair[1] && String(pair[1]).trim()) { return String(pair[1]).trim(); } } return ""; } function collectPayload(form, listSource) { var fd = new FormData(form); var raw = listSource.getAttribute("data-listid"); var listId; if (raw != null && String(raw).trim() !== "") { var n = parseInt(String(raw).trim(), 10); if (Number.isFinite(n)) listId = n; } var firstName = pickField(fd, [ "firstName", "first-name", "First-Name", "fname", "FirstName", "given_name", "given-name", ]) || findInputValueByDataName(form, /^first\s*name$/i) || findInputValueByDataName(form, /^firstname$/i) || findInputValueByDataName(form, /^given\s*name$/i); var lastName = pickField(fd, [ "lastName", "last-name", "Last-Name", "lname", "LastName", "surname", ]) || findInputValueByDataName(form, /^last\s*name$/i) || findInputValueByDataName(form, /^lastname$/i) || findInputValueByDataName(form, /^surname$/i); var city = pickField(fd, [ "city", "City", "town", "Town", "hometown", "HomeTown", ]) || findInputValueByDataName(form, /^city$/i) || findInputValueByDataName(form, /^town$/i) || findInputValueByDataName(form, /^hometown$/i); return { email: findEmail(form, fd), firstName: firstName, lastName: lastName, city: city, phone: pickField(fd, ["phone", "Phone", "tel", "telephone"]), phoneFull: pickField(fd, [ "phone_full", "phone-full", "Phone_Full", "Phone_full", ]), listId: listId, }; } /** * The input element intl-tel-input was bound to. Webflow uses `type="text"` + `iti__tel-input`, not * always `type="tel"`. */ function findIntlTelInput(form) { if (!(form instanceof HTMLFormElement)) return null; var byPluginAttr = form.querySelector("input[data-intl-tel-input-id]"); if (byPluginAttr) return byPluginAttr; var byClass = form.querySelector( "input.iti__tel-input, input.intl-tel-input, input.intl-phone" ); if (byClass) return byClass; var wrap = form.querySelector(".iti"); if (wrap) { var tel = wrap.querySelector('input[type="tel"]'); if (tel) return tel; var itiText = wrap.querySelector( "input.iti__tel-input, input.intl-tel-input, input.intl-phone" ); if (itiText) return itiText; var textTel = wrap.querySelector('input[type="text"][inputmode="tel"]'); if (textTel) return textTel; } return form.querySelector('input[type="tel"]'); } function getIntlTelInstance(input) { if (!input || typeof window.intlTelInput === "undefined") return null; var g = window.intlTelInput; if (typeof g.getInstance !== "function") return null; try { return g.getInstance(input); } catch (e) { return null; } } /** * Fills `payload.phoneFull` from intl-tel-input when present (requires utils: `iti.promise` first). * If the instance exists but `isValidNumber()` is false, clears `phone_full` for Community. * Sets `payload._intlTelMeta` when ITI is present: `{ hasInstance, isEmpty, isValid }` for submit validation. */ async function mergePhoneFullFromIntlTel(form, payload) { log("intl-tel: mergePhoneFullFromIntlTel start"); var input = findIntlTelInput(form); if (!input) { log("intl-tel: no intl-tel input found — using FormData phone_full only"); return; } if (typeof window.intlTelInput === "undefined") { log("intl-tel: window.intlTelInput missing — skip plugin merge"); return; } var iti = getIntlTelInstance(input); if (!iti) { log("intl-tel: getInstance returned null — using FormData phone_full only"); return; } try { if (iti.promise && typeof iti.promise.then === "function") { log("intl-tel: awaiting iti.promise (utils)"); await iti.promise; log("intl-tel: utils ready"); } } catch (e) { warn("intl-tel: utils failed to load", e); return; } var rawVal = String(input.value || "").trim(); var isEmpty = rawVal === ""; var countryData = typeof iti.getSelectedCountryData === "function" ? iti.getSelectedCountryData() : null; if (!isEmpty && !countryData) { payload._intlTelMeta = { hasInstance: true, isEmpty: false, isValid: false, }; log("intl-tel: digits entered but no country — clear phone_full, will validate"); payload.phoneFull = ""; return; } try { if (typeof iti.isValidNumber !== "function" || typeof iti.getNumber !== "function") { log("intl-tel: isValidNumber/getNumber not available — skip"); payload._intlTelMeta = { hasInstance: true, isEmpty: isEmpty, isValid: false, }; return; } if (!iti.isValidNumber()) { payload._intlTelMeta = { hasInstance: true, isEmpty: isEmpty, isValid: false, }; log("intl-tel: not valid — clear phone_full for Community", { masked: maskPhoneForLog(payload.phoneFull), isEmpty: isEmpty, }); payload.phoneFull = ""; return; } var num = iti.getNumber(); if (num && String(num).trim()) { payload.phoneFull = String(num).trim(); log("intl-tel: phone_full set from getNumber", maskPhoneForLog(payload.phoneFull)); if (!payload.phone || !String(payload.phone).trim()) { payload.phone = payload.phoneFull; } } payload._intlTelMeta = { hasInstance: true, isEmpty: isEmpty, isValid: true, }; } catch (e) { warn("intl-tel: isValidNumber/getNumber failed", e); payload._intlTelMeta = { hasInstance: true, isEmpty: isEmpty, isValid: false, }; } } /** True when ITI is present, user typed something, and the number is not valid (incomplete / wrong country). */ function shouldAbortForInvalidIntlPhone(payload) { var m = payload._intlTelMeta; return !!(m && m.hasInstance && !m.isEmpty && !m.isValid); } /** Focus phone, optional country dropdown, and native validation message. */ function flagInvalidIntlPhone(form) { var input = findIntlTelInput(form); if (!input) return; input.focus(); var iti = getIntlTelInstance(input); if (iti && typeof iti.openDropdown === "function") { try { iti.openDropdown(); } catch (e) { /* ignore */ } } var msg = "Enter a valid phone number including country code (or clear the field)."; if (typeof input.setCustomValidity === "function") { input.setCustomValidity(msg); input.reportValidity(); var clear = function () { input.setCustomValidity(""); input.removeEventListener("input", clear); input.removeEventListener("change", clear); }; input.addEventListener("input", clear); input.addEventListener("change", clear); } } function hasCommunityPhone(payload) { return ( payload.phoneFull != null && String(payload.phoneFull).trim() !== "" ); } /** Best-effort; failures are logged and the rest of the flow continues. */ async function postCommunitySubscribe(payload) { log("community: POST start", COMMUNITY_URL, { phone: maskPhoneForLog(payload.phoneFull), hasEmail: !!payload.email, hasGivenName: !!payload.firstName, hasSurname: !!payload.lastName, hasCity: !!payload.city, }); try { var body = { phone_full: String(payload.phoneFull).trim(), }; var em = payload.email != null && String(payload.email).trim(); if (em) body.email = String(em); var gn = payload.firstName != null && String(payload.firstName).trim(); if (gn) body.given_name = String(gn); var sn = payload.lastName != null && String(payload.lastName).trim(); if (sn) body.surname = String(sn); var ct = payload.city != null && String(payload.city).trim(); if (ct) body.city = String(ct); var res = await fetch(COMMUNITY_URL, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(body), }); var ctext = await res.text(); log("community: response", res.status, res.statusText, "bodyLen", ctext.length); if (!res.ok) { warn("community: error (continuing flow)", ctext.slice(0, 800)); } else if (ctext) { log("community: body preview", ctext.slice(0, 500)); } else { log("community: ok, empty body"); } } catch (err) { warn("community: fetch failed (continuing flow)", err); } } function getWaitText(submitBtn) { if (!submitBtn) return "Please wait..."; var w = submitBtn.getAttribute("data-wait"); if (w != null && String(w).trim() !== "") return String(w).trim(); return "Please wait..."; } function isInputSubmit(el) { return el.tagName === "INPUT" && el.type === "submit"; } function getSubmitLabel(submitBtn) { if (isInputSubmit(submitBtn)) return submitBtn.value; return submitBtn.textContent || ""; } function setSubmitLabel(submitBtn, text) { if (isInputSubmit(submitBtn)) submitBtn.value = text; else submitBtn.textContent = text; } /** Webflow-style: show data-wait (or “Please wait...”) and disable while our work runs. */ function applySubmitWaitState(submitBtn) { if (!submitBtn) { log("ui: applySubmitWaitState — no submit button"); return; } log("ui: submit wait state (data-wait / Please wait…)"); submitBtn[SUBMIT_LABEL_BACKUP] = getSubmitLabel(submitBtn); setSubmitLabel(submitBtn, getWaitText(submitBtn)); submitBtn.disabled = true; } function restoreSubmitButton(submitBtn) { if (!submitBtn) return; log("ui: restore submit button"); if (submitBtn[SUBMIT_LABEL_BACKUP] !== undefined) { setSubmitLabel(submitBtn, submitBtn[SUBMIT_LABEL_BACKUP]); delete submitBtn[SUBMIT_LABEL_BACKUP]; } submitBtn.disabled = false; } /** Restore label + enable, then native submit on the next macrotask (outside submit-handler stack). */ function handOffToWebflowSubmit(form, submitBtn) { log("flow: schedule native Webflow submit (next task)"); submitBtn = submitBtn || form.querySelector( 'input[type="submit"], button[type="submit"], button:not([type])' ); setTimeout(function () { if (submitBtn) { restoreSubmitButton(submitBtn); } if (!form.checkValidity()) { warn( "webflow: handoff aborted — HTML5 validation failed (fix highlighted fields and retry)" ); form.reportValidity(); return; } submitWebflowNative(form, submitBtn); }, 0); } function showFormState(form, state) { var messages = getMessageElements(form); var success = messages.success; var fail = messages.fail; log("UI state:", state, { hasSuccessEl: !!success, hasFailEl: !!fail, }); if (state === "success") { form.style.display = "none"; } else { form.style.display = ""; } if (success) { success.style.display = state === "success" ? "block" : "none"; success.hidden = state !== "success"; } if (fail) { fail.style.display = state === "error" ? "block" : "none"; fail.hidden = state !== "error"; } } /** * Second-phase submit after handoff. `requestSubmit` throws if HTML5 validation fails — clear SKIP_FLAG * and restore the button so the user is not stuck on “Please wait…”. */ function submitWebflowNative(form, submitBtn) { submitBtn = submitBtn || form.querySelector( 'input[type="submit"], button[type="submit"], button:not([type])' ); form[SKIP_FLAG] = true; try { if (typeof form.requestSubmit === "function") { log("webflow: requestSubmit()"); form.requestSubmit(submitBtn || undefined); } else { log("webflow: form.submit() fallback"); form.submit(); } } catch (err) { delete form[SKIP_FLAG]; warn( "webflow: requestSubmit did not run (validation or browser) — restored submit", err ); if (submitBtn) { restoreSubmitButton(submitBtn); } } } async function handleSubmit(e) { var form = e.target; if (!(form instanceof HTMLFormElement)) { return; } if (form[SKIP_FLAG]) { log("event: skip intercept — second pass after handoff (Webflow native)"); delete form[SKIP_FLAG]; return; } log("event: submit", { formId: form.id || "(no id)" }); var subscribeCb = getSubscribeCheckbox(form); var hasSubscribeCheckbox = !!subscribeCb; var subscribeChecked = !!(subscribeCb && subscribeCb.checked); /** After our work, use native Webflow submit + Webflow UI (any form with data-subscribe). */ var useWebflowHandoff = hasSubscribeCheckbox; /** POST /leads only when there is no email opt-out checkbox, or it is checked. */ var shouldPostLeads = !hasSubscribeCheckbox || subscribeChecked; var listSource = getListSource(form); if (!listSource) { log("decision: no [data-listid] — pass through to Webflow only"); return; } e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); log("intercept", { mode: useWebflowHandoff ? "Webflow handoff" : "leads + embedded success UI", formAction: form.getAttribute("action"), formId: form.id || "(no id)", dataListid: listSource.getAttribute("data-listid"), hasSubscribeCheckbox: hasSubscribeCheckbox, subscribeChecked: hasSubscribeCheckbox ? subscribeChecked : "(n/a)", shouldPostLeads: shouldPostLeads, }); var payload = collectPayload(form, listSource); await mergePhoneFullFromIntlTel(form, payload); log("payload after intl merge", { email: payload.email ? "(present)" : "(missing)", listId: payload.listId, phone: maskPhoneForLog(payload.phone), phoneFull: maskPhoneForLog(payload.phoneFull), intlTelMeta: payload._intlTelMeta || "(none)", }); if (shouldAbortForInvalidIntlPhone(payload)) { warn("validation: phone incomplete or invalid — fix or clear before submit"); flagInvalidIntlPhone(form); showFormState(form, "error"); return; } var submitBtn = form.querySelector( 'input[type="submit"], button[type="submit"], button:not([type])' ); applySubmitWaitState(submitBtn); try { if (hasCommunityPhone(payload)) { log("step: Community (phone_full present)"); await postCommunitySubscribe(payload); } else { log("step: skip Community — no phone_full"); } if (!payload.email) { warn("validation: no email"); if (useWebflowHandoff) { log("flow: no email — skip leads, Webflow only"); handOffToWebflowSubmit(form, submitBtn); } else { log("flow: no email — show error (leads only)"); showFormState(form, "error"); } return; } if (!shouldPostLeads) { log("flow: email list opt-out — skip /leads, Webflow only"); handOffToWebflowSubmit(form, submitBtn); return; } log("step: ActiveCampaign /leads"); var body = { email: payload.email }; if (payload.firstName) body.firstName = payload.firstName; if (payload.lastName) body.lastName = payload.lastName; if (payload.phone) body.phone = payload.phone; if (payload.city) body.city = payload.city; if (payload.listId !== undefined) body.listId = payload.listId; log("leads: POST", LEADS_URL, { hasListId: body.listId !== undefined, hasPhone: !!body.phone, hasCity: !!body.city, }); var res = await fetch(LEADS_URL, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(body), }); log("leads: response", res.status, res.statusText, res.url); if (useWebflowHandoff) { if (res.ok) { log("leads: ok — then Webflow"); try { var okJson = await res.json(); if (okJson) log("leads: body", okJson); } catch (ignore) {} } else { var errTextWf = await res.text(); warn("leads: error (Webflow will still submit)", errTextWf.slice(0, 800)); } handOffToWebflowSubmit(form, submitBtn); } else { if (res.ok) { try { var okJson2 = await res.json(); if (okJson2) log("leads: body", okJson2); } catch (ignore) {} log("flow: leads success — showFormState success"); showFormState(form, "success"); } else { var errText = await res.text(); warn("leads: error body", errText.slice(0, 800)); log("flow: leads error — showFormState error"); showFormState(form, "error"); } } } catch (err) { warn("leads: fetch threw", err); if (useWebflowHandoff) { log("flow: after leads throw — hand off to Webflow"); handOffToWebflowSubmit(form, submitBtn); } else { log("flow: after leads throw — showFormState error"); showFormState(form, "error"); } } finally { if (submitBtn && !useWebflowHandoff) { log("finally: restore submit (legacy leads-only path)"); restoreSubmitButton(submitBtn); } } } log( "initialized — [data-listid]: Community if phone; /leads when email opt-in; data-subscribe = email only" ); document.addEventListener("submit", handleSubmit, true); })();