Skip to content
X2TalentHire Talent
Sep 18, 2025Job Search

How to Build a Daily Product Design Jobs Radar

Carl Wheatley · Founder, X2Talent

This guide shows you how to auto-pull fresh Product Design jobs into Google Sheets every morning. It's simple, fast, and free to start.

You will build

  • A daily job sweep that finds new roles on top ATS sites
  • A Google Sheet that logs Company, Role, Link, and a short snapshot
  • Optional sweeps for Founding Designer, Leadership, and wide "Design" titles
  • Optional location filters for SF Bay Area and the United States

What you need

  • A Google account
  • One Google Sheet
  • A Google Cloud API key for Custom Search JSON API
  • A Programmable Search Engine (CSE) with ATS domains

Step 1. Make your Google Sheet

Create a sheet with a tab named Product Design Job Search.

Headers (Row 1): Date | Company | Role | Link | Company one-liner | Notes

Step 2. Turn on the Custom Search API and make an API key

  1. Go to Google Cloud Console → create a project.
  2. APIs & Services → Library → enable Custom Search JSON API.
  3. APIs & Services → Credentials → Create credentials → API key.
  4. Restrict the key to the Custom Search API. Save your key.

Tip: You can store keys in Apps Script → Project Settings → Script properties and read them with PropertiesService. See the optional code at the end.

Step 3. Create your Programmable Search Engine (CSE)

  1. Open the CSE control panel.
  2. Create a search engine.
  3. Add these sites to search and save. Set "Search only included sites":
boards.greenhouse.io
jobs.lever.co
jobs.ashbyhq.com
apply.workable.com
manatal.com
jobs.gem.com
myworkdayjobs.com
myworkdaysite.com
jobs.smartrecruiters.com
pinpointhq.com
ats.rippling.com
teamtailor.com
recruitee.com
jobs.jobvite.com
recruiting.ultipro.com
careers.icims.com
  1. Copy your Search engine ID (CSE ID).

Step 4. Add the Apps Script

In your sheet, go to Extensions → Apps Script. Paste this code and replace the placeholders at the top. Save.

/***** Daily Product Design Jobs Sweep *****/
/* ===== Config ===== */
const SHEET_NAME = "Product Design Job Search"; // must match tab name
const CSE_API_KEY = "YOUR_GOOGLE_API_KEY"; // restrict to Custom Search API
const CSE_ID = "YOUR_CSE_ID"; // from your Programmable Search Engine
const RESULTS_PER_QUERY = 5; // 1..10 per API call
const DATE_RESTRICT = 'd1'; // d1 = last day

/* ===== ATS domains and role filters ===== */
const ROLE_OR = '("Product Designer" OR "UX Designer" OR "UI Designer" OR "Design Engineer")';
const ATS_DOMAINS = [
  'site:boards.greenhouse.io','site:jobs.lever.co','site:jobs.ashbyhq.com',
  'site:apply.workable.com','site:manatal.com','site:jobs.gem.com',
  'site:myworkdayjobs.com','site:myworkdaysite.com','site:jobs.smartrecruiters.com',
  'site:pinpointhq.com','site:ats.rippling.com','site:teamtailor.com',
  'site:recruitee.com','site:jobs.jobvite.com','site:recruiting.ultipro.com',
  'site:careers.icims.com'
];
const QUERIES = ATS_DOMAINS.map(d => `${d} ${ROLE_OR}`);

/* ===== Main runner ===== */
function runDaily() {
  const sh = getOrCreateSheet_(SHEET_NAME);
  const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd');
  const rows = [];
  QUERIES.forEach(q => {
    const hits = cseSearch_(q, RESULTS_PER_QUERY);
    hits.forEach(h => {
      const pr = parseCompanyAndRole_(h.title);
      rows.push([today, pr.company || guessCompanyFromUrl_(h.link), pr.role, h.link, companyOneLinerFromSnippet_(h.snippet), '']);
    });
    Utilities.sleep(300);
  });
  if (!rows.length) return;
  const deduped = dedupeByColumn_(rows, 4); // de-dupe by Link
  sh.getRange(sh.getLastRow() + 1, 1, deduped.length, deduped[0].length).setValues(deduped);
}

/* ===== Helpers ===== */
function cseSearch_(q, num) {
  const url = 'https://www.googleapis.com/customsearch/v1?' + [
    'key=' + encodeURIComponent(CSE_API_KEY),
    'cx=' + encodeURIComponent(CSE_ID),
    'q=' + encodeURIComponent(q),
    'num=' + Math.min(num || 5, 10),
    'dateRestrict=' + DATE_RESTRICT
  ].join('&');
  const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
  if (res.getResponseCode() !== 200) {
    Logger.log('CSE error ' + res.getResponseCode() + ': ' + res.getContentText());
    return [];
  }
  const data = JSON.parse(res.getContentText() || '{}');
  return (data.items || []).map(it => ({ title: it.title || '', link: it.link || '', snippet: it.snippet || '' }));
}

function parseCompanyAndRole_(title) {
  const parts = title.split(' - ');
  const roleRe = /designer|design|ux|ui|engineer/i;
  if (parts.length === 2) {
    const a = parts[0].trim(), b = parts[1].trim();
    if (roleRe.test(a) && !roleRe.test(b)) return { company: b, role: a };
    if (roleRe.test(b) && !roleRe.test(a)) return { company: a, role: b };
  }
  return { company: '', role: title.slice(0, 80) };
}

function guessCompanyFromUrl_(url) {
  try {
    const host = new URL(url).host;
    const parts = host.split('.');
    const core = parts.length >= 2 ? parts[parts.length - 2] : host;
    const ats = ['greenhouse','lever','workday','ashbyhq','workable','manatal','gem','smartrecruiters','pinpointhq','rippling','teamtailor','recruitee','jobvite','ultipro','icims'];
    if (core && !ats.includes(core)) return core.charAt(0).toUpperCase() + core.slice(1);
  } catch(e) {}
  return '';
}

function companyOneLinerFromSnippet_(s) {
  if (!s) return '';
  return s.replace(/\s+/g, ' ').slice(0, 140);
}

function getOrCreateSheet_(name) {
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getSheetByName(name) || ss.insertSheet(name);
  if (sh.getLastRow() === 0) {
    sh.getRange(1,1,1,6).setValues([['Date','Company','Role','Link','Company one-liner','Notes']]);
  }
  return sh;
}

function dedupeByColumn_(rows, colIndex1Based) {
  const seen = new Set();
  const out = [];
  rows.forEach(r => {
    const key = r[colIndex1Based - 1];
    if (!key || !seen.has(key)) {
      if (key) seen.add(key);
      out.push(r);
    }
  });
  return out;
}

Run it once. Approve permissions.

Step 5. Set the daily schedule

Open Triggers in Apps Script. Add a trigger: Function runDaily, Type "Time driven", Daily at 8:05 am.

Done. Your sheet will fill each morning.

Step 6. Add a Founding Designer sweep (optional)

Add a new tab called Founding Designer Job Search. Then paste this under your code.

const SHEET_NAME_FOUNDING = "Founding Designer Job Search";
const QUERIES_FOUNDING = [
  'site:boards.greenhouse.io "Founding Designer"',
  'site:jobs.lever.co "Founding Designer"',
  'site:jobs.ashbyhq.com "Founding Designer"',
  'site:apply.workable.com "Founding Designer"',
  'site:manatal.com "Founding Designer"',
  'site:jobs.gem.com "Founding Designer"',
  'site:myworkdayjobs.com "Founding Designer"',
  'site:myworkdaysite.com "Founding Designer"',
  'site:jobs.smartrecruiters.com "Founding Designer"',
  'site:pinpointhq.com "Founding Designer"',
  'site:ats.rippling.com "Founding Designer"',
  'site:teamtailor.com "Founding Designer"',
  'site:recruitee.com "Founding Designer"',
  'site:jobs.jobvite.com "Founding Designer"',
  'site:recruiting.ultipro.com "Founding Designer"',
  'site:careers.icims.com "Founding Designer"'
];
function runFoundingDaily() {
  runSweepToSheet_(SHEET_NAME_FOUNDING, QUERIES_FOUNDING);
}
function runSweepToSheet_(sheetName, queries) {
  const sh = getOrCreateSheet_(sheetName);
  const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd');
  const rows = [];
  queries.forEach(q => {
    const hits = cseSearch_(q, RESULTS_PER_QUERY);
    hits.forEach(h => {
      const pr = parseCompanyAndRole_(h.title);
      rows.push([today, pr.company || guessCompanyFromUrl_(h.link), pr.role, h.link, companyOneLinerFromSnippet_(h.snippet), '']);
    });
    Utilities.sleep(300);
  });
  if (!rows.length) return;
  const deduped = dedupeByColumn_(rows, 4);
  sh.getRange(sh.getLastRow() + 1, 1, deduped.length, deduped[0].length).setValues(deduped);
}

Add a second trigger if you want it to run daily too.

Step 7. Add wide "Design" titles (optional)

Create a new tab called All Design Job Search. Use this wide title list.

const TITLES_WIDE = [
  "Product Designer","Senior Product Designer","Principal Designer","Staff Designer",
  "UX Designer","Senior UX Designer","Head of UX","VP, User Experience",
  "UI Designer","Design Engineer","Product Design Engineer",
  "Design Lead","Lead Designer","Head of Design","Design Manager","Design Director","Director of Design",
  "VP of Design","Chief Design Officer","Executive Creative Director","Creative Director",
  "Founding Designer","Design Systems","Design System","Service Designer","Industrial Designer",
  "Design Technologist","Creative Technologist","Prototyper",
  "Design Program Manager","Design Producer","Design Strategist","Design Ops","Design Operations Manager",
  "Experience Designer","Interaction Designer","Visual Designer","Human-Centered Designer",
  "UX Researcher","AR Designer","VR Designer","XR Designer"
];
const ROLE_OR_WIDE = '(' + TITLES_WIDE.map(t => '"' + t + '"').join(' OR ') + ')';
const TITLE_HAS_DESIGN = 'intitle:Design';
const ROLE_EXCLUDE = '-"Graphic Designer" -"Interior Designer" -CAD -"3D"';
const QUERIES_DESIGN_ANY = ATS_DOMAINS.map(d => `${d} (${TITLE_HAS_DESIGN} OR ${ROLE_OR_WIDE}) ${ROLE_EXCLUDE}`);
function runDesignAnyDaily() {
  runSweepToSheet_("All Design Job Search", QUERIES_DESIGN_ANY);
}

Add a trigger if you want this daily.

Step 8. Add location filters for SF Bay and US (optional)

Make two tabs: All Design — SF Bay Area and All Design — United States.

const FILTER_SF = '("San Francisco" OR "Bay Area" OR "San Jose" OR "Oakland" OR "Palo Alto" OR "Mountain View" OR "Sunnyvale" OR "Menlo Park" OR "Redwood City" OR "San Mateo" OR "Santa Clara")';
const FILTER_US = '("United States" OR "US" OR "Remote US")';
const QUERIES_DESIGN_SF = ATS_DOMAINS.map(d => `${d} (${TITLE_HAS_DESIGN} OR ${ROLE_OR_WIDE}) ${ROLE_EXCLUDE} ${FILTER_SF}`);
const QUERIES_DESIGN_US = ATS_DOMAINS.map(d => `${d} (${TITLE_HAS_DESIGN} OR ${ROLE_OR_WIDE}) ${ROLE_EXCLUDE} ${FILTER_US}`);
function runDesignSFDaily(){ runSweepToSheet_("All Design - SF Bay Area", QUERIES_DESIGN_SF); }
function runDesignUSDaily(){ runSweepToSheet_("All Design - United States", QUERIES_DESIGN_US); }

Step 9. Troubleshooting

  • 0 results. Add each domain to your CSE. Try dropping the location filter. Test one query in your browser with key and cx.
  • Sheet stays empty. Tab name does not match SHEET_NAME. Fix and run again.
  • 403 or API error. Rotate or restrict your API key to Custom Search API. Check quotas.
  • Too many dupes. De-dupe is by Link. Keep it.
  • Too noisy. Add more excludes in ROLE_EXCLUDE. Lower RESULTS_PER_QUERY.

Step 10. Nice extras

  • Add a "Founding Designer" tab for fast client hits.
  • Add a "Leadership" tab using only leadership titles.
  • Send results to Slack or email with a webhook.
  • Add columns for Priority and Source. Use filters to plan outreach.

Optional: keep keys in Script properties

// Save keys in Apps Script → Project Settings → Script properties
// Then read like this:
const CSE_API_KEY = PropertiesService.getScriptProperties().getProperty('CSE_API_KEY');
const CSE_ID = PropertiesService.getScriptProperties().getProperty('CSE_ID');

You now have a daily radar for Product Design jobs. Start with one daily sweep. Add more as you need. Keep it neat and simple.

Appendix: Set up Google Programmable Search Engine (CSE)

Use this when you want the script to search only the ATS sites you pick.

What you'll get: A CSE ID (also called cx). You will use this with your API key in Apps Script.

Part 1. Make the engine

  1. Open: programmablesearchengine.google.com and sign in.
  2. Click Add (or Create a search engine).
  3. In "Sites to search," paste these domains and press Add after each:
boards.greenhouse.io
jobs.lever.co
jobs.ashbyhq.com
apply.workable.com
manatal.com
jobs.gem.com
myworkdayjobs.com
myworkdaysite.com
jobs.smartrecruiters.com
pinpointhq.com
ats.rippling.com
teamtailor.com
recruitee.com
jobs.jobvite.com
recruiting.ultipro.com
careers.icims.com
  1. Give it a name like "ATS Jobs CSE."
  2. Click Create.
  3. Go to Setup in the left menu.
  4. Under "Sites to search," set "Search only included sites."
  5. Copy the Search engine ID. This is your CSE_ID.

Tip: You can add or remove domains anytime. Changes are live right away.

Part 2. Turn on the API and make a key

  1. Open console.cloud.google.com.
  2. Create a project or pick one.
  3. Go to APIs & Services → Library. Turn on Custom Search JSON API.
  4. Go to APIs & Services → Credentials → Create credentials → API key.
  5. Click the key → Restrict key.
  6. API restrictions: Restrict to Custom Search API.
  7. Save. Copy the key.

Security: If your key was ever posted in chat or a doc, rotate it.

Part 3. Plug into your script

In Apps Script config at the top:

const CSE_API_KEY = "YOUR_REAL_API_KEY";
const CSE_ID = "YOUR_REAL_CSE_ID";

Make sure your search helper includes the last-24-hours filter:

const DATE_RESTRICT = 'd1'; // last day

Part 4. Test that it works

Try a test call in your browser. Replace KEY and CX with your values:

https://www.googleapis.com/customsearch/v1?key=KEY&cx=CX&q=site:jobs.lever.co%20%22Product%20Designer%22&num=3&dateRestrict=d1

If you see JSON with items, it works. If you see an error, the message tells you what to fix.

Part 5. Common fixes

  • 0 results: The domain is missing in your CSE, or the 24-hour window is too tight for your filters. Add the domain, or remove the location filter and test again.
  • 400 Invalid cx: Wrong CSE ID. Copy it again from the CSE Setup page.
  • 403/Quota: Key not restricted to the right API, or you hit the daily quota. Check APIs & Services → Quotas.
  • Sheet is empty: Your sheet tab name doesn't match the SHEET_NAME in code.

Part 6. Nice to have

  • Make a second CSE for Leadership only, or for SF Bay vs United States.
  • Keep Workday and Oracle Cloud on a separate weekly engine to cut noise.
  • Log your CSE_ID and CSE_API_KEY in Script properties and read them with PropertiesService to keep secrets out of code.

Carl is a Former Product Designer Turned Design Recruiter. Carl is also a Mentor at Mento Design Academy. Before recruiting, he was a product designer working with many tech startups to design mobile apps. Carl is also the co-founder of a Meetup called Global UXD where he helps connect designers with each other and create new opportunities. Having completed Thinkful and DesignLab bootcamps before becoming a recruiter, he's an expert at helping designers land their first design roles. Find Carl on carlwheatly.com and LinkedIn.