| Category | Item | Tags |
|---|
Google Apps Script Backend Setup
Connect your Google Sheet so the library pulls data automatically on load.
1
Open your Google Sheet (the one you want to use as the backend). Go to Extensions → Apps Script.
2
Create two sheets named
Demand and Supply. Each needs columns: CATEGORY, ITEM, LINK, TAGS (comma-separated), and optionally MENU_GROUP to explicitly assign an entry to a menu section.3
Paste the Apps Script code below into the editor. Replace
YOUR_SHEET_ID with your actual Google Sheet ID (from the URL).4
First time: Deploy → New Deployment → Web app, "Execute as: Me", "Who has access: Anyone". Copy the deployment URL. Updating later: Deploy → Manage deployments → pencil → Version: New version. Editing the existing deployment keeps the same
/exec URL. Creating a new deployment issues a fresh URL and silently breaks this page.5
In this HTML file, find
APPS_SCRIPT_URL near the top of the script section and replace it with your deployment URL. The tool will then fetch live data on load.Apps Script Code (paste into your project)
// ─── Media.net Marketing Collaterals Library — Apps Script ───────
// Paste this into Extensions → Apps Script in your Google Sheet.
// Sheet must have two tabs named exactly: "Demand" and "Supply"
// Columns: A=Category B=Item C=Link D=Tags (comma-separated) E=MENU_GROUP (optional)
//
// MENU_GROUP (Column E) lets you explicitly assign any row to a
// specific menu section in the dashboard. Accepted values:
// Verticals
// Products & Solutions (Demand)
// Products & Solutions (Supply)
// Partnerships
// Case Studies
// Integration Guides
// RFIs & Pitches
// Personalized Partner Newsletters
// Other Tags
//
// Leave column E blank to use automatic category-based routing.
function doGet(e) {
const params = (e && e.parameter) ? e.parameter : {};
const tab = params.tab || 'Demand';
let payload;
try {
const ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(tab);
if (!ws) {
payload = { error: 'Tab not found: ' + tab };
} else {
const rows = ws.getDataRange().getValues();
// Skip header row (row 1), skip rows with no Item value
payload = { data: rows.slice(1).filter(r => r[1]).map(r => ({
category: String(r[0] || '').trim(),
item: String(r[1] || '').trim(),
link: String(r[2] || '').trim(),
tags: r[3] ? String(r[3]).split(',').map(t => t.trim()).filter(Boolean) : [],
menuGroup: r[4] ? String(r[4]).trim() : '' // optional explicit menu section
})) };
}
} catch (err) {
payload = { error: String(err) };
}
return reply(payload, params.callback);
}
// Add + delete from the dashboard. The page posts form-encoded with
// mode:'no-cors' and never reads the reply, so failures here are invisible
// to it — check Executions in the Apps Script editor when debugging.
function doPost(e) {
const p = (e && e.parameter) ? e.parameter : {};
const lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
const ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(p.tab || 'Demand');
if (!ws) return reply({ ok: false, error: 'Tab not found: ' + p.tab });
if (p.action === 'add') {
ws.appendRow([p.category || '', p.item || '', p.link || '', p.tags || '', p.menuGroup || '']);
} else if (p.action === 'delete') {
const rows = ws.getDataRange().getValues();
for (let i = rows.length - 1; i >= 1; i--) {
if (String(rows[i][1]).trim() === String(p.item).trim()) { ws.deleteRow(i + 1); break; }
}
}
return reply({ ok: true });
} catch (err) {
return reply({ ok: false, error: String(err) });
} finally {
lock.releaseLock();
}
}
// Returns JSONP when a ?callback= is present, plain JSON otherwise.
// The dashboard reads via JSONP because Apps Script cannot set CORS headers,
// so a normal fetch() breaks the moment the /exec redirect returns anything
// other than clean JSON.
function reply(obj, callback) {
const json = JSON.stringify(obj);
if (callback) {
return ContentService
.createTextOutput(callback + '(' + json + ');')
.setMimeType(ContentService.MimeType.JAVASCRIPT);
}
return ContentService
.createTextOutput(json)
.setMimeType(ContentService.MimeType.JSON);
}
Created by kenn.d | 2026