One-click export of a Zoom Docs meeting transcript to a clean, speaker-labeled markdown file. Works with the current Zoom Docs viewer.
Chrome / Edge / Firefox:
Safari, or if dragging doesn't work:
javascript:!async%20function()%7Bfunction%20e(e)%7Balert(%22Zoom%20%E2%86%92%20Markdown%3A%20%22%2Be)%7Dtry%7Bif(!%2F%5Ehub%5C.zoom%5C.us%24%2F.test(location.host))return%20e(%22Open%20the%20meeting's%20Zoom%20Docs%20page%20first%20(hub.zoom.us%2Fdoc%2F%E2%80%A6)%2C%20then%20click%20the%20bookmarklet%20from%20there.%22)%3Bvar%20t%3Dlocation.pathname.match(%2F%5C%2Fdoc%5C%2F(%5B%5E%2F%3F%23%5D%2B)%2F)%3Bif(!t)return%20e(%22This%20doesn't%20look%20like%20a%20Zoom%20Docs%20URL%20(need%20%2Fdoc%2F%3CfileId%3E).%22)%3Bvar%20n%3Dt%5B1%5D%2Co%3Dawait%20fetch(%22https%3A%2F%2Fhub.zoom.us%2Fnws%2Fcommon%2F2.0%2Fnak%3Fpms%3DHub%252CUser%253ABase%252CAICW%26src%3Daicw%22%2C%7Bcredentials%3A%22include%22%7D)%3Bif(!o.ok)return%20e(%22Sign%20into%20Zoom%20and%20reload%20the%20page%20(nak%20%22%2Bo.status%2B%22).%22)%3Bvar%20i%3D(await%20o.text()).trim()%3Bif(!i%7C%7Ci.length%3C100)return%20e(%22Couldn't%20fetch%20auth%20token.%22)%3Bvar%20a%3D%7Bauthorization%3A%22Bearer%20%22%2Bi%7D%2Cs%3Dawait%20fetch(%22https%3A%2F%2Fus01docs.zoom.us%2Fapi%2Ffile%2Ffiles%2Faction%2Fbatch_get%22%2C%7Bmethod%3A%22POST%22%2Ccredentials%3A%22include%22%2Cheaders%3AObject.assign(%7B%22content-type%22%3A%22application%2Fjson%22%7D%2Ca)%2Cbody%3AJSON.stringify(%7Bids%3A%5Bn%5D%7D)%7D)%3Bif(!s.ok)return%20e(%22File%20lookup%20failed%20(batch_get%20%22%2Bs.status%2B%22).%22)%3Bvar%20r%3Dawait%20s.json()%2Cc%3Dr%26%26r.successItems%26%26r.successItems%5B0%5D%3Bif(!c)return%20e(%22No%20access%20to%20this%20doc%2C%20or%20it%20doesn't%20exist.%22)%3Bvar%20d%3Dc.meetingNotes%26%26(c.meetingNotes.meetingId%7C%7Cc.meetingNotes.mainMeetingId)%3Bif(!d)return%20e(%22This%20doc%20isn't%20linked%20to%20a%20meeting%20transcript.%22)%3Bvar%20u%3D%22https%3A%2F%2Fus01docs.zoom.us%2Fapi%2Fbridge%2Fmeeting%2Ftranscripts%2Fv2%3FmeetingId%3D%22%2BencodeURIComponent(d)%2B%22%26fileId%3D%22%2BencodeURIComponent(n)%2Cm%3Dawait%20fetch(u%2C%7Bcredentials%3A%22include%22%2Cheaders%3Aa%7D)%3Bif(!m.ok)return%20e(%22Transcript%20fetch%20failed%20(%22%2Bm.status%2B%22).%22)%3Bvar%20l%3Dawait%20m.json()%2Ch%3Dl%26%26l.items%3Bif(!h%7C%7C!h.length)return%20e(%22No%20transcript%20available%20for%20this%20meeting.%22)%3Bvar%20f%3D%7B%7D%3B(l.speakers%7C%7C%5B%5D).forEach(function(e)%7Bf%5Be.userId%5D%3De.username%7C%7C%22Unknown%22%7D)%3Bvar%20p%3D%5B%5D%2Cg%3Dnull%2Ck%3Dnull%2Cv%3D%5B%5D%3Bfunction%20b()%7Bv.length%26%26p.push(%22**%22%2Bg%2B%22**%22%2B(k%3F%22%20%5B%22%2Bk%2B%22%5D%22%3A%22%22)%2B%22%5Cn%22%2Bv.join(%22%20%22))%7Dh.forEach(function(e)%7Bvar%20t%3Df%5Be.userId%5D%7C%7C%22Unknown%22%2Cn%3D(e.startTime%7C%7C%22%22).split(%22.%22)%5B0%5D%2Co%3D(e.text%7C%7C%22%22).trim()%3Bo%26%26(t!%3D%3Dg%26%26(b()%2Cv%3D%5B%5D%2Cg%3Dt%2Ck%3Dn)%2Cv.push(o))%7D)%2Cb()%3Bvar%20w%3D%22%23%20Transcript%5Cn%5Cn%22%2Bp.join(%22%5Cn%5Cn%22)%2B%22%5Cn%22%2CI%3D(c.title%7C%7C%22transcript%22).replace(%2F%5B%2F%5C%5C%3F%25*%3A%7C%22%3C%3E%5D%2Fg%2C%22-%22).trim()%2CU%3Dnew%20Blob(%5Bw%5D%2C%7Btype%3A%22text%2Fmarkdown%22%7D)%2Cj%3DURL.createObjectURL(U)%2Cy%3Ddocument.createElement(%22a%22)%3By.href%3Dj%2Cy.download%3DI%2B%22.md%22%2Cdocument.body.appendChild(y)%2Cy.click()%2Cy.remove()%2CURL.revokeObjectURL(j)%7Dcatch(C)%7Be(C%26%26C.message%7C%7CString(C))%7D%7D()%3B
https://hub.zoom.us/doc/<fileId>?…<meeting title>.md file downloads.The original bookmarklet at namolnad.github.io/zoom-to-markdown scraped speaker turns from the rendered DOM. It stopped working when Zoom shipped the current Zoom Docs viewer, which:
span[role="img"][aria-label] avatar elements — the old selector matches nothing.This version skips the DOM and calls the same JSON APIs the Zoom Docs single-page app uses:
GET hub.zoom.us/nws/common/2.0/nak?… — gets a fresh Bearer JWT (from your existing session).POST us01docs.zoom.us/api/file/files/action/batch_get — looks up the meetingId tied to the current file.GET us01docs.zoom.us/api/bridge/meeting/transcripts/v2?meetingId=…&fileId=… — returns the full transcript as JSON.The bookmarklet then groups consecutive utterances per speaker and downloads a markdown file.
The bookmarklet uses alert() to surface errors. Common ones:
hub.zoom.us/doc/….Full source and rebuild instructions at github.com/kdttocs/zoom-to-markdown.