Skip to content

Instantly share code, notes, and snippets.

@azanli
Last active May 20, 2026 17:32
Show Gist options
  • Select an option

  • Save azanli/bec39455efa7a6e6ab7d55dff6b9df37 to your computer and use it in GitHub Desktop.

Select an option

Save azanli/bec39455efa7a6e6ab7d55dff6b9df37 to your computer and use it in GitHub Desktop.
GitHub Actions Workflow Form Preset Generator (Bookmarklet)

GitHub Actions Workflow Form Preset Generator (Bookmarklet)

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.


πŸš€ Setup Instructions (One-Time Setup)

To add the generator tool to your browser:

  1. Create a temporary bookmark: Bookmark any random webpage (or press Ctrl+D / Cmd+D).
  2. Edit the bookmark: Right-click the newly created bookmark and select Edit (or open your Bookmark Manager).
  3. Change the Title: Name it something recognizable, like GitHub Workflow Bookmarklet(πŸˆβ€β¬›).
  4. 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);})();

πŸ” Source Code Deep Dive

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);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment