Build a URL Shortener with Apps Script Web App

You can build a fully functional URL shortener using just Apps Script and Google Sheets — no domain, no server, no cost. The Web App handles redirects, the Sheet stores the mappings.

Sheet structure

Create a sheet named Links with these columns:

| A: Short Code | B: Long URL | C: Created At | D: Click Count |

The Web App script

function doGet(e) { const code = e.parameter.c; if (!code) { return HtmlService.createHtmlOutput('<h2>URL Shortener</h2><p>No code provided.</p>'); } const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Links'); const data = sheet.getDataRange().getValues(); for (let i = 1; i < data.length; i++) { if (data[i][0] === code) { const longUrl = data[i][1]; // Increment click count sheet.getRange(i + 1, 4).setValue((data[i][3] || 0) + 1); // Redirect return HtmlService.createHtmlOutput( `<script>window.location.href = "${longUrl}";</script> <p>Redirecting... <a href="${longUrl}">Click here if not redirected</a></p>` ); } } return HtmlService.createHtmlOutput('<h2>404 — Short link not found.</h2>'); }

Add a short link

function addShortLink(code, longUrl) { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Links'); // Check for duplicate code const data = sheet.getDataRange().getValues(); const exists = data.some((row, i) => i > 0 && row[0] === code); if (exists) { Logger.log(`Code "${code}" already exists.`); return false; } sheet.appendRow([code, longUrl, new Date(), 0]); Logger.log(`Added: ${code}${longUrl}`); return true; } // Usage: // addShortLink('docs', 'https://docs.google.com/...'); // addShortLink('sheet', 'https://docs.google.com/spreadsheets/...');

Auto-generate a short code

function generateShortCode(length = 6) { const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; let code = ''; for (let i = 0; i < length; i++) { code += chars[Math.floor(Math.random() * chars.length)]; } return code; } function createShortLink(longUrl) { let code; const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Links'); const data = sheet.getDataRange().getValues(); const existingCodes = new Set(data.slice(1).map(r => r[0])); do { code = generateShortCode(); } while (existingCodes.has(code)); sheet.appendRow([code, longUrl, new Date(), 0]); const scriptUrl = ScriptApp.getService().getUrl(); const shortUrl = `${scriptUrl}?c=${code}`; Logger.log(`Short URL: ${shortUrl}`); return shortUrl; }

View click analytics

function getClickReport() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Links'); const data = sheet.getDataRange().getValues().slice(1); const sorted = data.sort((a, b) => (b[3] || 0) - (a[3] || 0)); Logger.log('=== Click Report ==='); sorted.forEach(([code, url, created, clicks]) => { Logger.log(`${code}: ${clicks || 0} clicks → ${url}`); }); }

Deploy and use

  1. Click Deploy > New deployment > Web App.
  2. Set Execute as: Me.
  3. Set Who has access: Anyone.
  4. Copy the deployment URL — e.g. https://script.google.com/macros/s/YOUR_ID/exec.

Your short links look like: https://script.google.com/macros/s/YOUR_ID/exec?c=docs

Tips

  • The redirect uses JavaScript window.location.href — it's not a true HTTP 301/302 redirect, but it works in any browser.
  • For a cleaner URL, point a custom domain's redirect to your Web App URL (many domain registrars support URL forwarding).
  • Each time you update the script, create a new deployment to keep the URL stable.
  • You can add basic authentication by checking e.parameter.key against a stored secret before allowing link creation.