Ad-Free Keyword Mixer for Google Ads (Paste, Mix, Export)

February 14, 2026 NEW

Ad-free Keyword Mixer you can host on your own site. Paste word lists, pick a match type, and instantly generate Google Ads-ready keyword combinations — with dedupe + CSV export.

Why it exists: Keyword tools are often bloated, slow, or plastered with ads and tracking. Sometimes you just want a clean “combine these lists” utility that respects your time (and your clipboard).

What it does:

  • Mixes 2 or 3 lists into combinations (A×B or A×B×C).
  • Supports match types: Broad, Phrase, Exact.
  • Optional: prepend/append “modifiers” (like locations or intent words).
  • Dedupe + trims whitespace, removes empties.
  • Exports as a Google Ads-friendly CSV (one keyword per line, with match type formatting).

Install:

  1. Create: /tools/keyword-mixer/
  2. Save the file below as: /tools/keyword-mixer/index.html
  3. Visit: /tools/keyword-mixer/

Tip: Keep your lists “clean”: one item per line (no commas). This tool is meant to be fast and predictable.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Ad-Free Keyword Mixer for Google Ads</title>
<meta name="description" content="Ad-free keyword mixer: paste lists, mix into Google Ads-ready keywords, choose match type, dedupe, and export to CSV." />
<meta name="robots" content="index,follow" />
<style>
  :root{
    --bg:#0b0f16; --panel:#111a26; --panel2:#0c1420;
    --text:#e8eef8; --muted:rgba(232,238,248,.72);
    --line:rgba(255,255,255,.10); --accent:#2a8cff;
    --shadow:0 18px 55px rgba(0,0,0,.45); --r:16px;
    --ok:#7CFF9A; --warn:#FFD37C;
  }
  *{box-sizing:border-box}
  body{margin:0;background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
  .wrap{max-width:1100px;margin:0 auto;padding:18px}
  .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:14px;margin:12px 0;box-shadow:var(--shadow)}
  .top{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center}
  .pill{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(42,140,255,.14);border:1px solid rgba(42,140,255,.25);font-weight:900;font-size:12px}
  .muted{color:var(--muted)}
  .grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:12px}
  .c4{grid-column:span 4}
  .c12{grid-column:span 12}
  @media(max-width:980px){.c4{grid-column:span 12}}
  textarea{
    width:100%;min-height:220px;resize:vertical;
    padding:10px 12px;border-radius:14px;border:1px solid var(--line);
    background:var(--panel2);color:var(--text);outline:none;
    font:650 13px/1.4 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  }
  input,select{
    width:100%;padding:10px 12px;border-radius:12px;border:1px solid var(--line);
    background:var(--panel2);color:var(--text);outline:none;
    font:700 13px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
  }
  .btn{
    display:inline-flex;align-items:center;justify-content:center;gap:8px;
    border:0;border-radius:12px;padding:10px 12px;background:var(--accent);
    color:#06101a;font-weight:900;cursor:pointer;
  }
  .btn2{
    display:inline-flex;align-items:center;justify-content:center;gap:8px;
    border:1px solid var(--line);border-radius:12px;padding:10px 12px;background:transparent;
    color:var(--text);font-weight:900;cursor:pointer;
  }
  .row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
  @media(max-width:780px){.row2{grid-template-columns:1fr}}
  .kpi{display:flex;gap:14px;flex-wrap:wrap;margin-top:8px}
  .kpi b{font-size:16px}
  .out{
    white-space:pre-wrap;word-break:break-word;
    padding:12px;border-radius:14px;border:1px solid var(--line);
    background:var(--panel2);min-height:240px;
    font:650 13px/1.4 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  }
</style>
</head>
<body>
<div class="wrap">

  <div class="card">
    <div class="top">
      <div>
        <div class="pill">Ad-Free Tool</div>
        <h1 style="margin:10px 0 6px;font-size:22px">Keyword Mixer for Google Ads</h1>
        <div class="muted">Paste lists → mix → choose match type → export. No ads, no tracking, no bloat.</div>
      </div>
    </div>
    <div class="kpi muted">
      <div>Mode: <b id="modeLbl">A×B</b></div>
      <div>Keywords: <b id="countLbl">0</b></div>
      <div class="muted">Tip: keep lists one item per line.</div>
    </div>
  </div>

  <div class="grid">
    <div class="card c4">
      <b>List A</b>
      <div class="muted" style="margin:6px 0 10px">Example: services (plumber, dentist, lawyer)</div>
      <textarea id="a" placeholder="plumber
electrician
roof repair"></textarea>
    </div>

    <div class="card c4">
      <b>List B</b>
      <div class="muted" style="margin:6px 0 10px">Example: intent or modifiers (near me, 24/7, emergency)</div>
      <textarea id="b" placeholder="near me
24/7
emergency"></textarea>
    </div>

    <div class="card c4">
      <b>List C (optional)</b>
      <div class="muted" style="margin:6px 0 10px">Example: locations (Detroit, Chicago, Miami)</div>
      <textarea id="c" placeholder="Detroit
Chicago
Miami"></textarea>
    </div>

    <div class="card c12">
      <div class="row2">
        <div>
          <div class="muted" style="font-size:12px;margin-bottom:6px">Match type</div>
          <select id="match">
            <option value="broad">Broad</option>
            <option value="phrase">Phrase</option>
            <option value="exact">Exact</option>
          </select>
        </div>
        <div>
          <div class="muted" style="font-size:12px;margin-bottom:6px">Output format</div>
          <select id="format">
            <option value="lines">One keyword per line</option>
            <option value="csv">CSV (Keyword)</option>
          </select>
        </div>
      </div>

      <div class="row2" style="margin-top:12px">
        <div>
          <div class="muted" style="font-size:12px;margin-bottom:6px">Optional prepend (applies to every keyword)</div>
          <input id="pre" placeholder="best" />
        </div>
        <div>
          <div class="muted" style="font-size:12px;margin-bottom:6px">Optional append (applies to every keyword)</div>
          <input id="post" placeholder="services" />
        </div>
      </div>

      <div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:12px">
        <button class="btn" id="mixBtn" type="button">🔀 Mix keywords</button>
        <button class="btn2" id="copyBtn" type="button">📋 Copy</button>
        <button class="btn2" id="dlBtn" type="button">⬇️ Download CSV</button>
        <button class="btn2" id="clearBtn" type="button">🧹 Clear</button>
      </div>
    </div>

    <div class="card c12">
      <b>Output</b>
      <div class="muted" style="margin:6px 0 10px">Deduped, trimmed, ready to paste into Google Ads.</div>
      <div id="out" class="out"></div>
    </div>
  </div>

</div>

<script>
(function(){
  var $ = function(id){ return document.getElementById(id); };

  function lines(s){
    return String(s||"")
      .split(/\r?\n/)
      .map(function(x){ return x.trim(); })
      .filter(function(x){ return x.length > 0; });
  }

  function dedupe(arr){
    var seen = Object.create(null);
    var out = [];
    for (var i=0;i<arr.length;i++){
      var k = arr[i];
      if (seen[k]) continue;
      seen[k] = 1;
      out.push(k);
    }
    return out;
  }

  function applyMatchType(k, match){
    if (match === "phrase") return '"' + k + '"';
    if (match === "exact")  return '[' + k + ']';
    return k; // broad
  }

  function joinParts(pre, core, post){
    var s = [pre, core, post].filter(Boolean).join(" ").replace(/\s+/g," ").trim();
    return s;
  }

  function mix(){
    var A = lines($("a").value);
    var B = lines($("b").value);
    var C = lines($("c").value);

    var pre  = $("pre").value.trim();
    var post = $("post").value.trim();

    var match = $("match").value;
    var fmt   = $("format").value;

    var out = [];

    if (!A.length || !B.length) {
      $("out").textContent = "Add at least List A and List B.";
      $("countLbl").textContent = "0";
      $("modeLbl").textContent = "A×B";
      return;
    }

    if (C.length) {
      $("modeLbl").textContent = "A×B×C";
      for (var i=0;i<A.length;i++){
        for (var j=0;j<B.length;j++){
          for (var k=0;k<C.length;k++){
            var core = A[i] + " " + B[j] + " " + C[k];
            out.push(joinParts(pre, core, post));
          }
        }
      }
    } else {
      $("modeLbl").textContent = "A×B";
      for (var i2=0;i2<A.length;i2++){
        for (var j2=0;j2<B.length;j2++){
          var core2 = A[i2] + " " + B[j2];
          out.push(joinParts(pre, core2, post));
        }
      }
    }

    // normalize whitespace + dedupe
    out = out.map(function(x){ return x.replace(/\s+/g," ").trim(); });
    out = dedupe(out);

    // apply match type
    out = out.map(function(x){ return applyMatchType(x, match); });

    $("countLbl").textContent = String(out.length);

    if (fmt === "csv") {
      // single-column CSV: Keyword
      var csv = ["Keyword"].concat(out.map(function(x){
        // CSV escape double quotes
        var v = String(x).replace(/"/g,'""');
        return '"' + v + '"';
      })).join("\n");
      $("out").textContent = csv;
      $("dlBtn").disabled = false;
      $("dlBtn").dataset.csv = csv;
    } else {
      $("out").textContent = out.join("\n");
      $("dlBtn").disabled = true;
      $("dlBtn").dataset.csv = "";
    }
  }

  function copyOut(){
    var t = $("out").textContent || "";
    if (!t.trim()) return;
    navigator.clipboard.writeText(t).catch(function(){
      // fallback
      var ta = document.createElement("textarea");
      ta.value = t;
      document.body.appendChild(ta);
      ta.select();
      document.execCommand("copy");
      document.body.removeChild(ta);
    });
  }

  function downloadCSV(){
    var csv = $("dlBtn").dataset.csv || "";
    if (!csv) return;
    var blob = new Blob([csv], {type:"text/csv;charset=utf-8"});
    var url = URL.createObjectURL(blob);
    var a = document.createElement("a");
    a.href = url;
    a.download = "keywords.csv";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

  function clearAll(){
    $("a").value = "";
    $("b").value = "";
    $("c").value = "";
    $("pre").value = "";
    $("post").value = "";
    $("out").textContent = "";
    $("countLbl").textContent = "0";
    $("modeLbl").textContent = "A×B";
  }

  $("mixBtn").addEventListener("click", mix);
  $("copyBtn").addEventListener("click", copyOut);
  $("dlBtn").addEventListener("click", downloadCSV);
  $("clearBtn").addEventListener("click", clearAll);

  // auto-mix on change (lightweight)
  ["a","b","c","pre","post","match","format"].forEach(function(id){
    $(id).addEventListener("input", function(){
      // don’t spam until they have A & B
      if ($("a").value.trim() && $("b").value.trim()) mix();
    });
    $(id).addEventListener("change", function(){
      if ($("a").value.trim() && $("b").value.trim()) mix();
    });
  });
})();
</script>
</body>
</html>

Comments (2)

  • admin
    Feb 14, 2026 8:06 PM
    Nice — yours is the full Ads-grade version; mine’s a lightweight drop-in mixer. Different goals, both useful depending on the use case.
  • Mediadeboer.nl
    Feb 14, 2026 7:16 PM
    Cool concept, but this looks very familiar 😄 I built the original version of this tool back in January — with Google Ads validation, multi-match-type support, combination patterns, and auto-clean built in. Check it out: https://mediadeboer.nl/google-ads-keyword-mixer/

← Back to all scripts