Another method is to pinch it from the HTML. You can run this from the browser console while in Campaign. It's an async function, but top level await is available in recent browsers, so if you want to assign it to a variable you should be able to with: const myVar = await getAllAttributes() though it does also output to console. You might want to throw it into a variable though, to sort alphabetically or whatever. The regex is pretty strict on the attributes in the HTML being written as "attr_<name here>" ... but I don't think there's an alternatives that work for Roll20 anyway? const getAllAttributes = async () => { const getCharsheetDataFB = async () => { const e = `/editor/charsheetdata/ ${ window . campaign_id } ` ; return new Promise (( t , i ) => { let n = 0 ; const o = () => { $ . ajax ({ url : e , type : "GET" , error : e => { n < 2 ? ( n += 1 , o ()) : i ( e ) }, success : e => { const i = JSON . parse ( e ); t ( i ) } }) }; o () }) } const decodeCharsheetData = async ( data ) => { if ( data . html ) data . html = window . BASE64 . decode ( data . html ); if ( data . css ) data . css = window . BASE64 . decode ( data . css ); if ( data . translation ) data . translation = window . BASE64 . decode ( data . translation ); return data ; } const sheetData = await getCharsheetDataFB (). then ( async ( csd ) => decodeCharsheetData ( csd )); const html = sheetData . html , attributeRx = /"attr_ ([^ " ] + ) / gi , matches = html . matchAll ( attributeRx ); let output = []; matches . forEach ( m => { if ( m [ 1 ] && ! output . includes ( m [ 1 ])) output . push ( m [ 1 ]); }); console . info ( output . filter ( v => v )); return output . filter ( v => v ); }