Tutorials

Pull Live UK Football Odds into Google Sheets — No Code Required (2026)

Pull Live UK Football Odds into Google Sheets — No Code Required (2026)

You don't need to be a developer to use live UK bookmaker odds data. If you can use Google Sheets, you can pull live odds from Bet365, Sky Bet, Paddy Power, William Hill, and 20+ other UK bookmakers directly into a spreadsheet using Google Apps Script and UKOddsApi.

This is perfect for tipsters tracking odds movement, content creators building "best odds" tables for blog posts, analysts comparing bookmaker margins, or anyone who works in spreadsheets and needs live football odds.

What you'll build

A Google Sheets workbook that:

  • Pulls live odds from all UK bookmakers into your spreadsheet
  • Auto-refreshes on a schedule (every 5, 15, or 30 minutes)
  • Highlights the best and worst odds per selection
  • Calculates bookmaker margins (overround)
  • Works for any league — Premier League, Championship, Champions League, and more

Step 1: Set up your Google Sheet

  1. Create a new Google Sheet
  2. Rename the first tab to "Config"
  3. In cell A1, type API Key and in B1, paste your UKOddsApi key
  4. In cell A2, type League and in B2, type premier-league
  5. Create a second tab called "Odds"

Step 2: Add the Apps Script

Go to Extensions → Apps Script, delete any existing code, and paste this:

/**
 * UK Football Odds Tracker
 * Pulls live odds from 25+ UK bookmakers into Google Sheets.
 * Powered by UKOddsApi — https://ukoddsapi.com
 *
 * Setup:
 * 1. Get a free API key at ukoddsapi.com
 * 2. Paste it in the Config tab, cell B1
 * 3. Run fetchOdds() or set up a trigger for auto-refresh
 */

const BASE_URL = "https://api.ukoddsapi.com";

function getConfig() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Config");
  return {
    apiKey: sheet.getRange("B1").getValue(),
    league: sheet.getRange("B2").getValue() || "premier-league",
  };
}

function apiCall(endpoint, params) {
  const config = getConfig();
  const queryString = Object.entries(params || {})
    .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
    .join("&");

  const url = `${BASE_URL}${endpoint}${queryString ? "?" + queryString : ""}`;

  const response = UrlFetchApp.fetch(url, {
    headers: { "X-Api-Key": config.apiKey },
    muteHttpExceptions: true,
  });

  if (response.getResponseCode() !== 200) {
    throw new Error(`API error ${response.getResponseCode()}: ${response.getContentText()}`);
  }

  return JSON.parse(response.getContentText());
}

/**
 * Main function — fetches odds and writes to the Odds tab.
 * Run this manually or set up a time-based trigger.
 */
function fetchOdds() {
  const config = getConfig();
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Odds");

  // Clear existing data
  sheet.clearContents();

  // Get today's date
  const today = Utilities.formatDate(new Date(), "UTC", "yyyy-MM-dd");

  // Fetch fixtures
  let events;
  try {
    const data = apiCall("/v1/football/events", {
      league: config.league,
      schedule_date: today,
    });
    events = data.events || [];
  } catch (e) {
    sheet.getRange("A1").setValue("Error: " + e.message);
    return;
  }

  if (events.length === 0) {
    // Try upcoming if nothing today
    const data = apiCall("/v1/football/events", { league: config.league });
    events = (data.events || []).slice(0, 10);
  }

  // Headers
  let row = 1;
  sheet.getRange(row, 1).setValue("UK Football Odds — " + config.league.replace(/-/g, " ").toUpperCase());
  sheet.getRange(row, 1).setFontSize(14).setFontWeight("bold");
  row++;
  sheet.getRange(row, 1).setValue("Last updated: " + new Date().toLocaleString());
  sheet.getRange(row, 1).setFontColor("#888888");
  row += 2;

  // Process each fixture
  for (const event of events.slice(0, 10)) {
    try {
      // Get best odds
      const bestData = apiCall(`/v1/football/events/${event.event_id}/odds/best`, {
        package: "core",
        odds_format: "decimal",
      });

      // Get all odds
      const allData = apiCall(`/v1/football/events/${event.event_id}/odds`, {
        package: "core",
        odds_format: "decimal",
      });

      // Match header
      sheet.getRange(row, 1).setValue("⚽ " + event.event_title);
      sheet.getRange(row, 1).setFontSize(12).setFontWeight("bold");
      sheet.getRange(row, 3).setValue("Kick-off: " + event.kickoff_utc);
      row++;

      // Process Win Market
      for (const market of allData.markets) {
        if (market.market_name !== "Win Market") continue;

        // Get unique bookmaker list
        const bookmakers = [];
        const seen = new Set();
        for (const sel of market.selections) {
          if (!seen.has(sel.bookmaker_name)) {
            seen.add(sel.bookmaker_name);
            bookmakers.push(sel.bookmaker_name);
          }
        }

        // Column headers: Selection | Bookmaker1 | Bookmaker2 | ... | Best | Best Bookie
        sheet.getRange(row, 1).setValue(market.market_name).setFontWeight("bold");
        row++;

        const headerRow = row;
        sheet.getRange(row, 1).setValue("Selection");
        for (let i = 0; i < bookmakers.length; i++) {
          sheet.getRange(row, i + 2).setValue(bookmakers[i]);
        }
        sheet.getRange(row, bookmakers.length + 2).setValue("BEST");
        sheet.getRange(row, bookmakers.length + 3).setValue("Best Bookie");

        // Bold and colour the header row
        sheet.getRange(row, 1, 1, bookmakers.length + 3)
          .setFontWeight("bold")
          .setBackground("#f0f0f0");
        row++;

        // Group selections
        const selections = {};
        for (const sel of market.selections) {
          if (!selections[sel.selection_name]) {
            selections[sel.selection_name] = {};
          }
          selections[sel.selection_name][sel.bookmaker_name] = sel.odds;
        }

        // Find best odds per selection from the best endpoint
        const bestLookup = {};
        for (const bmarket of bestData.markets) {
          if (bmarket.market_name !== "Win Market") continue;
          for (const sel of bmarket.selections) {
            bestLookup[sel.selection_name] = {
              odds: sel.odds,
              bookmaker: sel.bookmaker_name,
            };
          }
        }

        // Write data rows
        for (const [selName, bookieOdds] of Object.entries(selections)) {
          sheet.getRange(row, 1).setValue(selName);

          const allOdds = [];
          for (let i = 0; i < bookmakers.length; i++) {
            const bname = bookmakers[i];
            const odds = bookieOdds[bname];
            if (odds) {
              sheet.getRange(row, i + 2).setValue(odds);
              allOdds.push({ odds, col: i + 2 });
            }
          }

          // Best odds
          const best = bestLookup[selName];
          if (best) {
            sheet.getRange(row, bookmakers.length + 2)
              .setValue(best.odds)
              .setFontWeight("bold")
              .setFontColor("#059669");
            sheet.getRange(row, bookmakers.length + 3)
              .setValue(best.bookmaker)
              .setFontColor("#059669");
          }

          // Highlight best and worst in the row
          if (allOdds.length > 1) {
            const maxOdds = Math.max(...allOdds.map((o) => o.odds));
            const minOdds = Math.min(...allOdds.map((o) => o.odds));
            for (const o of allOdds) {
              if (o.odds === maxOdds) {
                sheet.getRange(row, o.col).setBackground("#dcfce7"); // Green
              } else if (o.odds === minOdds) {
                sheet.getRange(row, o.col).setBackground("#fef2f2"); // Red
              }
            }
          }

          row++;
        }

        // Margin row
        sheet.getRange(row, 1).setValue("Margin").setFontStyle("italic");
        for (let i = 0; i < bookmakers.length; i++) {
          const bname = bookmakers[i];
          let totalProb = 0;
          let count = 0;
          for (const selOdds of Object.values(selections)) {
            if (selOdds[bname]) {
              totalProb += 1 / selOdds[bname];
              count++;
            }
          }
          if (count >= 3) {
            const margin = ((totalProb - 1) * 100).toFixed(1) + "%";
            const cell = sheet.getRange(row, i + 2);
            cell.setValue(margin).setFontStyle("italic");
            const marginVal = (totalProb - 1) * 100;
            if (marginVal < 3) cell.setFontColor("#059669");
            else if (marginVal < 5) cell.setFontColor("#d97706");
            else cell.setFontColor("#dc2626");
          }
        }
        row++;
      }

      row += 2; // Space between fixtures
    } catch (e) {
      sheet.getRange(row, 1).setValue("Error loading " + event.event_title + ": " + e.message);
      row += 2;
    }
  }

  // Auto-resize columns
  for (let i = 1; i <= 20; i++) {
    sheet.autoResizeColumn(i);
  }

  SpreadsheetApp.getActiveSpreadsheet().toast("Odds updated!", "✅ Success", 3);
}

/**
 * Set up auto-refresh. Run this once.
 * Creates a trigger that refreshes odds every 15 minutes.
 */
function setupAutoRefresh() {
  // Remove existing triggers
  const triggers = ScriptApp.getProjectTriggers();
  for (const trigger of triggers) {
    if (trigger.getHandlerFunction() === "fetchOdds") {
      ScriptApp.deleteTrigger(trigger);
    }
  }

  // Create new trigger — every 15 minutes
  ScriptApp.newTrigger("fetchOdds")
    .timeBased()
    .everyMinutes(15)
    .create();

  SpreadsheetApp.getActiveSpreadsheet().toast(
    "Auto-refresh set to every 15 minutes",
    "✅ Trigger Created",
    5
  );
}

/**
 * Stop auto-refresh.
 */
function stopAutoRefresh() {
  const triggers = ScriptApp.getProjectTriggers();
  for (const trigger of triggers) {
    if (trigger.getHandlerFunction() === "fetchOdds") {
      ScriptApp.deleteTrigger(trigger);
    }
  }
  SpreadsheetApp.getActiveSpreadsheet().toast("Auto-refresh stopped", "🛑 Stopped", 3);
}

/**
 * Add a custom menu to the spreadsheet.
 */
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("🇬🇧 UK Odds")
    .addItem("🔄 Refresh Odds Now", "fetchOdds")
    .addItem("⏰ Start Auto-Refresh (15 min)", "setupAutoRefresh")
    .addItem("🛑 Stop Auto-Refresh", "stopAutoRefresh")
    .addToUi();
}

Step 3: Run it

  1. Save the script (Ctrl+S)
  2. Go back to your spreadsheet
  3. Reload the page — you'll see a new "🇬🇧 UK Odds" menu
  4. Click 🇬🇧 UK Odds → 🔄 Refresh Odds Now
  5. Authorise the script when prompted (it needs permission to make API calls)

Your spreadsheet will fill with live odds from every UK bookmaker, colour-coded with green (best odds) and red (worst odds) highlighting, plus bookmaker margin calculations.

Step 4: Set up auto-refresh

Click 🇬🇧 UK Odds → ⏰ Start Auto-Refresh to automatically update every 15 minutes. The script uses Google's time-based triggers, so it runs even when you're not looking at the sheet.

To stop auto-refresh: 🇬🇧 UK Odds → 🛑 Stop Auto-Refresh

Customisation

Change the league: Edit cell B2 in the Config tab. Options include premier-league, championship, league-one, league-two, champions-league, fa-cup, and more.

Change refresh interval: Edit the setupAutoRefresh function — change everyMinutes(15) to everyMinutes(5) or everyMinutes(30).

Add more markets: The script currently shows the Win Market. To add BTTS and Over/Under, remove the if (market.market_name !== "Win Market") continue; filter in the fetchOdds function.

Show fractional odds: Change odds_format: "decimal" to odds_format: "fractional" in the API calls.

Use cases

Tipsters: Build "best odds" tables for your tips and embed them in blog posts or Twitter threads. The spreadsheet auto-updates, so your published odds stay current.

Content creators: Generate odds comparison data for articles, YouTube videos, or social media posts. Export as CSV or copy-paste formatted tables.

Analysts: Track bookmaker margins over time. Copy the Odds tab to a new sheet every matchday to build a historical record of which bookmakers consistently offer the best value.

Personal betting: Before placing a bet, check all 25+ UK bookmakers in one glance. The green highlighting instantly shows you where to place your bet for the best return.

API endpoints used

Endpoint Purpose
GET /v1/football/events Fetch fixtures by league and date
GET /v1/football/events/{id}/odds All bookmaker odds
GET /v1/football/events/{id}/odds/best Best odds per selection

UKOddsApi delivers odds from 25+ UK bookmakers into any tool — Python, JavaScript, Google Sheets, or anything that can make an HTTP request. Get your free API key at ukoddsapi.com