• facebook
  • twitter
  • linkedin

Rank the Top Candidates for Every Job in HubSpot — A Custom Code Action Guide

Matching applicants to open roles is only half the battle; you still need to identify which candidates rise to the top. HubSpot’s workflows can filter on properties, but they lack a true ranking mechanism. The custom coded action below closes that gap by:
  • Taking each Job Match record (a custom object that stores a numeric score for a Contact–Job pairing),
  • Ranking all Job-Match records belonging to the same Job ID, and
  • Flagging the top 20 % of matches (at least two) with is_top_applicant = true.

While the example focuses on recruiting, this ranking technique applies anywhere you need to score and surface the best records—think partner programs, grant applications, lead prioritization, or warranty claim triage.

When to Use This Pattern

Scenario Why It Helps
Recruiting platforms Auto-highlight the strongest applicants for each open job so recruiters spend time on the best fits first.
Channel / partner programs Score partner applications and flag the top tier for expedited onboarding.
Grants & scholarships Rank submissions and mark those above the funding threshold.
Lead scoring & routing Identify the top-X % leads for each campaign and trigger fast-track workflows.

Step-by-Step Implementation

1. Set Up the Custom Objects & Properties

Object Key Properties Purpose
Job (custom object) job_id, employer_id, job details The open role the employer wants to fill.
Job Match (custom object) job_id (lookup to Job)
contact_id (lookup to Contact)
score (number)
is_top_applicant (boolean)
Holds the match score for a Contact ↔ Job pair and the top applicant flag.

2. Generate a Private App Token

  1. Settings → Integrations → Private Apps → Create app
  2. Name it “Job-Match Ranking”.
  3. Enable scopes:
    crm.objects.custom.read & crm.objects.custom.write
  4. Copy the token and paste it into the custom code action’s Secrets as JOB_MATCH_RANKER.

3. Drop the Custom Code Action Into Your Workflow

Create a Job-Match–based workflow triggered whenever a new Job Match is created or its score changes. Add a Custom Code action and paste the code below. Replace the hard-coded object type ID and token with your own.


/**
 * Tag the top-20 % (min-2) Job-Match records for a Job.
 * Runtime: Node.js 20
 */
const hubspot = require('@hubspot/api-client');

/* — constants — */
const PRIVATE_APP_TOKEN        = process.env.JOB_MATCH_RANKER; // from Secrets
const JOB_MATCH_OBJECT_TYPE_ID = '2-45931025';  // ✔️ update for your portal
const TOP_PERCENT   = 0.20;  // 20 %
const MIN_COUNT     = 2;     // at least two flagged

exports.main = async (event, callback) => {
  const api = new hubspot.Client({ accessToken: PRIVATE_APP_TOKEN });

  /* 1. current Job-Match ID */
  const jobMatchId =
        event.inputFields['hs_object_id'] ||
        (event.object && event.object.objectId);

  if (!jobMatchId) {
    console.log('❌ No hs_object_id supplied; skipping run.');
    return callback({ outputFields: { flaggedCount: 0 } });
  }

  /* 2. look up its job_id */
  const thisMatch = await api.crm.objects.basicApi.getById(
    JOB_MATCH_OBJECT_TYPE_ID,
    jobMatchId,
    ['job_id']
  );
  const jobId = thisMatch.properties.job_id;

  if (!jobId) {
    console.log(`❌ Job-Match ${jobMatchId} missing job_id; aborting.`);
    return callback({ outputFields: { flaggedCount: 0 } });
  }

  /* 3. gather ALL matches for that job */
  const searchBody = {
    filterGroups: [{
      filters: [{ propertyName: 'job_id', operator: 'EQ', value: jobId }]
    }],
    properties: ['score', 'is_top_applicant']
  };
  const { results } = await api.crm.objects.searchApi.doSearch(
    JOB_MATCH_OBJECT_TYPE_ID,
    searchBody
  );

  if (!results.length) {
    console.log(`ℹ️  No matches found for job ${jobId}.`);
    return callback({ outputFields: { flaggedCount: 0 } });
  }

  /* 4. rank & choose the cut-off */
  results.sort((a, b) => Number(b.properties.score) - Number(a.properties.score));
  const cutoff = Math.max(MIN_COUNT, Math.ceil(results.length * TOP_PERCENT));

  /* 5. batch-update the top records */
  const updates = results.map((rec, idx) => ({
    id: rec.id,
    properties: { is_top_applicant: idx < cutoff }
  }));

  while (updates.length) {
    const batch = updates.splice(0, 100);
    await api.crm.objects.batchApi.update(JOB_MATCH_OBJECT_TYPE_ID, { inputs: batch });
  }

  callback({ outputFields: { flaggedCount: cutoff } });
};

4. Trigger Next-Step Automation

  • “Top applicant?”is_top_applicant equals Yes → Notify recruiters, create tasks, or move the candidate to an Interview pipeline.
  • Else → keep the match in the regular review queue.

5. Test the Workflow

  1. Create three Job-Match records for the same Job with varying score values.
  2. Manually enroll them (or trigger via score updates).
  3. Verify that the top 20 % (at least two) have is_top_applicant = true, and the rest are false.
  4. Check the workflow history to confirm the code action processed and the branch logic fired.

Results & Benefits

  • Objective short-listing — your best candidates are surfaced automatically.
  • Recruiter efficiency — teams focus on interviewing instead of spreadsheet jockeying.
  • Flexible logic — tweak TOP_PERCENT or MIN_COUNT for any use case.
  • Cross-industry ready — swap “Job” and “Job-Match” for any parent-child scoring model.

Wrapping Up

HubSpot alone can store scores, but turning those scores into actionable tiers requires custom logic. With a concise Node.js custom code action, you can rank child records, flag the winners, and let workflows take it from there. Whether you’re matching people to jobs, partners to programs, or leads to campaigns, this pattern keeps your team laser-focused on the top opportunities.

Have questions or want to explore other ranking recipes? Let’s elevate your Ops Hub game!

Comments (0)
Other

Insights You Might Like

Automatically Tag...

HubSpot stores heaps of data about your contacts, but it doesn’t natively track
Read More

Auto-Populate Deals from...

When a subscription is created in HubSpot—often via Stripe, Chargebee or a...
Read More

Auto-Merge Call-Tracking...

Tools like CallRail push phone calls straight into HubSpot, creating contacts...
Read More