Tired of repeatedly filling out the exact same input parameters every time you manually trigger a GitHub Actions workflow?
This utility is a meta-bookmarklet (a bookmarklet that generates custom bookmarklets). Fill out your workflow form once, click this generator bookmarklet, and it will instantly create a brand new, single-purpose bookmarklet containing your exact inputs. Next time, just click your custom preset bookmarklet to instantly populate the form!
It supports all standard GitHub workflow inputs, including text fields, dropdowns (select), checkboxes, and radio buttons.
To add the generator tool to your browser:
- Create a temporary bookmark: Bookmark any random webpage (or press
Ctrl+D/Cmd+D). - Edit the bookmark: Right-click the newly created bookmark and select Edit (or open your Bookmark Manager).
- Change the Title: Name it something recognizable, like
GitHub Workflow Bookmarklet(πββ¬). - Paste the Code: Clear the URL field entirely, and paste the entire JavaScript block below into it:
Tip: You can triple-click anywhere inside the box below to select the entire line instantly.
javascript:(async()=>{const delay=ms=>new Promise(r=>setTimeout(r,ms));const validHost=location.hostname==="github.com";const validPath=location.pathname.includes("/actions/workflows");if(!validHost||!validPath){console.warn(" β οΈ This bookmarklet must be run on a GitHub Actions workflow page.\n\nHow to use:\n1. Navigate to github.com repo β Actions β Workflows\n2. Open a workflow\n3. Click 'Run workflow'\n4. Fill out the form with desired values\n5. Run this bookmarklet");return;}const d=document.querySelector("details");if(d&&!d.open){d.children[0].click();await delay(2000);}const els=[...document.querySelectorAll('[id^="input_"]')].sort((a,b)=>parseInt(a.id.split("_")[1])-parseInt(b.id.split("_")[1]));const model=els.map(el=>{if(el.tagName==="SELECT")return{select:el.value};if(el.type==="checkbox")return{checkbox:el.checked};if(el.type==="radio"){if(el.checked)return{radio:{name:el.name,value:el.value}};return null;}return{text:el.value};}).filter(Boolean);console.log("Captured model:",model);const fn=async function(){const delay=ms=>new Promise(r=>setTimeout(r,ms));if(location.hostname!=="github.com"||!location.pathname.includes("/actions/workflows")){console.warn(" β οΈ This bookmarklet only works on GitHub Actions workflow pages.");return;}const d=document.querySelector("details");if(d&&!d.open){d.children[0].click();await delay(2000);}const model=MODEL_PLACEHOLDER;function setValue(id,e){const el=document.getElementById(id);if(!el)return;if(e.text!==undefined){el.value=e.text;el.dispatchEvent(new Event("input",{bubbles:true}));el.dispatchEvent(new Event("change",{bubbles:true}));}if(e.select!==undefined){el.value=e.select;el.dispatchEvent(new Event("change",{bubbles:true}));}if(e.checkbox!==undefined){el.checked=e.checkbox;el.dispatchEvent(new Event("change",{bubbles:true}));}if(e.radio!==undefined){document.querySelectorAll('input[name="'+e.radio.name+'"]').forEach(r=>{if(r.value===e.radio.value){r.checked=true;r.dispatchEvent(new Event("change",{bubbles:true}));}});}}model.forEach((e,i)=>setValue("input_"+i,e));};const fnString=fn.toString().replace("MODEL_PLACEHOLDER",JSON.stringify(model));const bookmarklet="javascript:("+fnString+")()";const wrap=document.createElement("div");wrap.style.cssText="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;";const modal=document.createElement("div");modal.style.cssText="background:white;padding:20px;border-radius:12px;text-align:center;box-shadow:0 10px 30px rgba(0,0,0,0.3);max-width:400px;position:relative;";const title=document.createElement("h2");title.innerText="Drag to Bookmarks";const desc=document.createElement("p");desc.innerText="Drag the button below to your bookmarks bar to save this preset.";const link=document.createElement("a");link.href=bookmarklet;link.innerText=" βοΈ Save Bookmarklet";link.style.cssText="display:inline-block;margin-top:15px;padding:10px 16px;background:#0366d6;color:white;border-radius:8px;text-decoration:none;cursor:grab;";const close=document.createElement("span");close.innerText="β";close.style.cssText="position:absolute;top:10px;right:15px;cursor:pointer;font-size:18px;";close.onclick=()=>wrap.remove();modal.appendChild(close);modal.appendChild(title);modal.appendChild(desc);modal.appendChild(link);wrap.appendChild(modal);document.body.appendChild(wrap);})();
Curious about how this works under the hood? Here is the un-minified script with comments outlining exactly how it maps your form data and outputs the drag-and-drop installer:
(async () => {
const delay = ms => new Promise(r => setTimeout(r, ms));
// 1. Guard rails to ensure script only runs in the appropriate context
const validHost = location.hostname === "github.com";
const validPath = location.pathname.includes("/actions/workflows");
if (!validHost || !validPath) {
console.warn(
"β οΈ This bookmarklet must be run on a GitHub Actions workflow page.\n\n" +
"How to use:\n1. Navigate to github.com repo β Actions β Workflows\n" +
"2. Open a workflow\n3. Click 'Run workflow'\n4. Fill out the form\n5. Run this tool."
);
return;
}
// 2. Open the 'Run workflow' configuration menu if it is currently closed
const d = document.querySelector("details");
if (d && !d.open) {
d.children[0].click();
await delay(2000); // Allow time for GitHub's DOM transitions
}
// 3. Find and sort all active inputs prefixed with GitHub's dynamic "input_" ID layout
const els = [...document.querySelectorAll('[id^="input_"]')].sort(
(a, b) => parseInt(a.id.split("_")[1]) - parseInt(b.id.split("_")[1])
);
// 4. Map the DOM elements into a serialized state model array
const model = els.map(el => {
if (el.tagName === "SELECT") return { select: el.value };
if (el.type === "checkbox") return { checkbox: el.checked };
if (el.type === "radio") {
if (el.checked) return { radio: { name: el.name, value: el.value } };
return null;
}
return { text: el.value };
}).filter(Boolean);
console.log("Captured Form Model:", model);
// 5. Define the structural function engine that runs inside the *generated* preset bookmarklet
const fn = async function() {
const delay = ms => new Promise(r => setTimeout(r, ms));
if (location.hostname !== "github.com" || !location.pathname.includes("/actions/workflows")) {
console.warn("β οΈ This bookmarklet only works on GitHub Actions workflow pages.");
return;
}
const d = document.querySelector("details");
if (d && !d.open) {
d.children[0].click();
await delay(2000);
}
// This token placeholder will be textually replaced by the generator with a static JSON array string
const model = MODEL_PLACEHOLDER;
function setValue(id, e) {
const el = document.getElementById(id);
if (!el) return;
// Assign the text/select values and fire inputs events to trip framework hooks
if (e.text !== undefined) {
el.value = e.text;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
}
if (e.select !== undefined) {
el.value = e.select;
el.dispatchEvent(new Event("change", { bubbles: true }));
}
if (e.checkbox !== undefined) {
el.checked = e.checkbox;
el.dispatchEvent(new Event("change", { bubbles: true }));
}
if (e.radio !== undefined) {
document.querySelectorAll('input[name="' + e.radio.name + '"]').forEach(r => {
if (r.value === e.radio.value) {
r.checked = true;
r.dispatchEvent(new Event("change", { bubbles: true }));
}
});
}
}
// Populate every captured configuration field sequentially
model.forEach((e, i) => setValue("input_" + i, e));
};
// 6. Inject the current static form configuration array straight into the payload string
const fnString = fn.toString().replace("MODEL_PLACEHOLDER", JSON.stringify(model));
const bookmarklet = "javascript:(" + fnString + ")()";
// 7. Render out a clean modal window so the user can easily grab their link
const wrap = document.createElement("div");
wrap.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;";
const modal = document.createElement("div");
modal.style.cssText = "background:white;padding:20px;border-radius:12px;text-align:center;box-shadow:0 10px 30px rgba(0,0,0,0.3);max-width:400px;position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;";
const title = document.createElement("h2");
title.style.cssText = "margin:0 0 10px 0;font-size:20px;color:#24292e;";
title.innerText = "Drag to Bookmarks";
const desc = document.createElement("p");
desc.style.cssText = "font-size:14px;color:#586069;margin:0 0 15px 0;line-height:1.5;";
desc.innerText = "Drag the button below to your bookmarks bar to save this preset.";
const link = document.createElement("a");
link.href = bookmarklet;
link.innerText = "βοΈ Save Bookmarklet";
link.style.cssText = "display:inline-block;margin-top:10px;padding:10px 16px;background:#2ea44f;color:white;border-radius:6px;text-decoration:none;font-weight:6px;cursor:grab;font-weight:600;box-shadow:0 1px 0 rgba(27,31,35,0.1);";
const close = document.createElement("span");
close.innerText = "β";
close.style.cssText = "position:absolute;top:12px;right:16px;cursor:pointer;font-size:16px;color:#586069;font-weight:bold;";
close.onclick = () => wrap.remove();
modal.appendChild(close);
modal.appendChild(title);
modal.appendChild(desc);
modal.appendChild(link);
wrap.appendChild(modal);
document.body.appendChild(wrap);
})();