Power Platform Bookmarklets

Quick browser tools for Power Platform devs. Drag bookmarklets to your bookmarks bar.

⚠️ Can't drag? Some browsers block dragging. Use the manual install steps below each bookmarklet.

📋 Get Table Metadata

Extracts Dataverse table schema from a model-driven app view and copies it as a markdown table. Perfect for pasting into Claude, Copilot, or ChatGPT.

✨ New: Now includes choice/picklist values with their IDs and labels!

Install: Drag this to your bookmarks bar →

Get Table Metadata
Manual install (if drag doesn't work)
  1. Right-click your bookmarks bar → Add page or Add bookmark
  2. Name: Get Table Metadata
  3. URL: Copy the code below and paste it
javascript:(function(){const e=new URLSearchParams(window.location.search).get('etn');if(!e)return alert('No entity in URL. Go to a table view first.');const t=window.location.origin,n=`${t}/api/data/v9.2/EntityDefinitions(LogicalName='${e}')`,a=`${n}?$select=LogicalName&$expand=Attributes($select=LogicalName,DisplayName,AttributeType)`,o=`${n}/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options)`;Promise.all([fetch(a).then(r=>r.json()),fetch(o).then(r=>r.json())]).then(([d,p])=>{const opts={};p.value.forEach(i=>{i.OptionSet?.Options&&(opts[i.LogicalName]=i.OptionSet.Options.map(o=>`${o.Value}: ${o.Label?.LocalizedLabels?.[0]?.Label||o.Value}`).join(', '))});const rows=d.Attributes.filter(a=>!a.LogicalName.match(/^(created|modified|owner|owning|statecode|statuscode|versionnumber|importsequencenumber|overriddencreatedon|timezoneruleversionnumber|utcconversiontimezonecode)/)).map(a=>{const name=a.DisplayName?.LocalizedLabels?.[0]?.Label||'',opt=opts[a.LogicalName]?` [${opts[a.LogicalName]}]`:'';return`| ${a.LogicalName} | ${name} | ${a.AttributeType}${opt} |`}).join('\n'),table=`**Table: ${e}**\n\n| LogicalName | DisplayName | AttributeType |\n|-------------|-------------|---------------|\n${rows}`;navigator.clipboard.writeText(table).then(()=>alert(`✓ Schema copied!\n\n${d.Attributes.length} total, ${rows.split('\n').length} shown\n${Object.keys(opts).length} choice fields`))}).catch(err=>alert('Error: '+err.message))})();
Usage
  1. Navigate to a table view in a model-driven app
  2. Click the bookmarklet
  3. Schema is copied to clipboard

What gets included:

📄 View source code

⚡ Get Flow JSON

Extracts Power Automate cloud flow definition from Dataverse and displays it in a formatted viewer. Perfect for analyzing flow structure, sharing with AI tools, or documentation.

Install: Drag this to your bookmarks bar →

Get Flow JSON
Manual install (if drag doesn't work)
  1. Right-click your bookmarks bar → Add page or Add bookmark
  2. Name: Get Flow JSON
  3. URL: Copy the code below and paste it
javascript:(function(){const history=JSON.parse(localStorage.getItem('paFlowHistory')||'[]');let flowId=prompt(history.length>0?'Enter Flow ID (or leave empty for recent flows):':'Enter Flow ID:');let selectedOrg=null;if(!flowId&&history.length>0){const list=history.map((f,i)=>`${i+1}. ${f.name} (${f.actions} actions) - ${f.org.split('.')[0]}`).join('\n');const choice=prompt(`Recent flows:\n\n${list}\n\nEnter number (1-${history.length}) or paste Flow ID:`);if(choice&&choice.match(/^\d+$/)){const idx=parseInt(choice)-1;if(idx>=0&&idx{const query=useUnique?`workflows?$filter=workflowidunique eq ${flowId}&$select=workflowid,workflowidunique,clientdata,name,category`:`workflows?$filter=workflowid eq ${flowId}&$select=workflowid,workflowidunique,clientdata,name,category`;const r=await fetch(`https://${orgUrl}/api/data/v9.2/${query}`,{credentials:'include',headers:{'Accept':'application/json','OData-MaxVersion':'4.0','OData-Version':'4.0'}});if(!r.ok)throw new Error(`HTTP ${r.status}`);return r.json();};const searchByName=async()=>{const name=prompt('Flow not found by ID.\n\nThis can happen with flows in the Default Solution.\n\nEnter part of the flow name to search:');if(!name)return null;const query=`workflows?$filter=contains(name,'${name}') and category eq 5&$select=workflowid,workflowidunique,name&$top=20`;const r=await fetch(`https://${orgUrl}/api/data/v9.2/${query}`,{credentials:'include',headers:{'Accept':'application/json','OData-MaxVersion':'4.0','OData-Version':'4.0'}});if(!r.ok)throw new Error(`HTTP ${r.status}`);return r.json();};const showFlow=(wf)=>{const json=JSON.parse(wf.clientdata);const def=json.properties?.definition||{};const conn=json.properties?.connectionReferences||{};const trigs=def.triggers||{};const acts=def.actions||{};const trigNames=Object.keys(trigs);const trigDetails=trigNames.length>0?getTriggerDetails(trigs[trigNames[0]],trigNames[0]):{type:'Unknown',details:''};function getTriggerDetails(t,name){if(!t)return{type:'Unknown',details:''};const type=t.type||'Unknown';let details='';if(type==='OpenApiConnectionWebhook'){const entity=t.inputs?.parameters?.['subscriptionRequest/entityname']||'';const msg=t.inputs?.parameters?.['subscriptionRequest/message'];const msgMap={1:'Create',2:'Update',3:'Delete',4:'Create or Update or Delete'};details=entity?`Table: ${entity}`:'';if(msg)details+=` | Event: ${msgMap[msg]||msg}`;}else if(type==='Recurrence'){const interval=t.recurrence?.interval||'';const freq=t.recurrence?.frequency||'';details=interval&&freq?`Every ${interval} ${freq}`:'Scheduled';}else if(type==='Request'){const kind=t.kind||'';details=kind==='Button'?'Manual (Button)':'HTTP Request';}else if(type==='ApiConnectionWebhook'){const opId=t.inputs?.host?.operationId||'';details=opId||'Webhook';}return{type:type,details:details,name:name};}function extractVariables(){const vars={};Object.entries(acts).forEach(([name,action])=>{if(action.type==='InitializeVariable'&&action.inputs?.variables){action.inputs.variables.forEach(v=>{vars[v.name]={type:v.type,value:v.value,usedIn:[]}});}});Object.keys(vars).forEach(varName=>{vars[varName].usedIn=Object.entries(acts).filter(([name,action])=>{const actStr=JSON.stringify(action);return actStr.includes(`variables('${varName}')`)||actStr.includes(`variables(\"${varName}\")`);}).map(([name])=>name);});return vars;}const variables=extractVariables();function groupVariablesByType(vars){const grouped={};Object.entries(vars).forEach(([name,info])=>{const type=info.type||'unknown';if(!grouped[type])grouped[type]=[];grouped[type].push({name:name,...info});});return grouped;}const groupedVars=groupVariablesByType(variables);function minimizeForAI(json){const def=json.properties?.definition||{};const conn=json.properties?.connectionReferences||{};const mini={trigger:{},actions:{},connections:{}};const trig=Object.entries(def.triggers||{})[0];if(trig){const[name,t]=trig;mini.trigger={name:name,type:t.type};if(t.type==='OpenApiConnectionWebhook'){mini.trigger.table=t.inputs?.parameters?.['subscriptionRequest/entityname'];mini.trigger.event=t.inputs?.parameters?.['subscriptionRequest/message'];}else if(t.type==='Recurrence'){mini.trigger.schedule={interval:t.recurrence?.interval,frequency:t.recurrence?.frequency};}else if(t.type==='Request'){mini.trigger.kind=t.kind;}}Object.entries(def.actions||{}).forEach(([name,action])=>{const a={type:action.type};if(action.runAfter)a.runAfter=Object.keys(action.runAfter);if(action.type==='InitializeVariable'||action.type==='SetVariable'){a.variable=action.inputs?.variables?.[0]?.name||action.inputs?.name;a.varType=action.inputs?.variables?.[0]?.type;}else if(action.type==='If'){a.expression=action.expression;}else if(action.type==='Foreach'||action.type==='Until'){a.iterates=action.foreach||action.expression;}else if(action.type==='OpenApiConnection'){a.operation=action.inputs?.host?.operationId;a.connection=action.inputs?.host?.connectionName;}else if(action.type==='Workflow'){a.childFlow=action.inputs?.host?.workflowReferenceName;}if(action.actions)a.hasNestedActions=Object.keys(action.actions).length;if(action.else?.actions)a.hasElse=Object.keys(action.else.actions).length;mini.actions[name]=a;});Object.entries(conn).forEach(([key,ref])=>{mini.connections[key]=ref.api?.name||key;});return mini;}function highlightJSON(obj){let str=JSON.stringify(obj,null,2);str=str.replace(/&/g,'&').replace(//g,'>');str=str.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g,function(match){let cls='number';if(/^\"/.test(match)){if(/:$/.test(match)){cls='key';}else{cls='string';}}else if(/true|false/.test(match)){cls='boolean';}else if(/null/.test(match)){cls='null';}return''+match+'';});return str;}const historyEntry={id:wf.workflowidunique||wf.workflowid,name:wf.name,org:orgUrl,timestamp:new Date().toISOString(),actions:Object.keys(acts).length,variables:Object.keys(variables).length};const newHistory=[historyEntry,...history.filter(f=>!(f.id===(wf.workflowidunique||wf.workflowid)&&f.org===orgUrl))].slice(0,10);localStorage.setItem('paFlowHistory',JSON.stringify(newHistory));const w=window.open('','_blank','width=1200,height=800');w.document.write(`${wf.name}

${wf.name}

Org: ${orgUrl}
Dataverse ID:
${wf.workflowid}
${searchId.toLowerCase()===wf.workflowid.toLowerCase()?'Used':''}
Maker Portal ID:
${wf.workflowidunique||'N/A'}
${wf.workflowidunique&&searchId.toLowerCase()===wf.workflowidunique.toLowerCase()?'Used':''}
Actions
${Object.keys(acts).length}
Variables
${Object.keys(variables).length}
Connections
${Object.keys(conn).length}
🎯 Trigger
${trigDetails.name}
${trigDetails.type}
${trigDetails.details?`
${trigDetails.details}
`:''}
${Object.keys(variables).length>0?`

📝 Variables (${Object.keys(variables).length})

${Object.entries(groupedVars).map(([type,vars])=>`
${type}
${vars.map(v=>`
${v.name}
Type: ${v.type}${v.value!==undefined?' | Initial: '+JSON.stringify(v.value):''}
${v.usedIn.length>0?`
Used in: ${v.usedIn.map(a=>`${a}`).join('')}
`:''}
`).join('')}
`).join('')}
`:''}

📝 Full Definition

${highlightJSON(json)}

🔗 Connection References

${highlightJSON(conn)}