Hiring
Jobs and applications on a pipeline per job, candidates from config.subjects, scorecards per stage, interviews, and hire, reject and withdraw events for the app to act on.
hiring is an applicant tracking system on the pipelines foundation. A job is open or closed, each job has its own pipeline of stages, and an application moves a candidate through it, collecting interviews and scorecards on the way, until it ends hired, rejected or withdrawn. A candidate is any record in config.subjects, a parties person by default, so a candidate who applies twice is one record with two applications.
Reading needs hiring.read, moving applications, scheduling and scoring needs hiring.write, and jobs need hiring.manage. Admins get every permission and members none: give recruiters a role with hiring.read and hiring.write. An interviewer on an interview reads it and scores the application without those permissions.
better-supabase sql add hiringThe module needs tenant, access, subjects and pipelines. Installing it binds the application type in the pipelines module with one pipeline per job (scope: "job_id"):
| Category | Outcome | Default stages |
|---|---|---|
active | open | Applied, Screening, Interview, Offer |
hired | success | Hired |
rejected | failure | Rejected |
withdrawn | failure | Withdrawn |
Each job's first application seeds its pipeline; an admin adds or renames stages per job with the pipelines client. The category sets the application's state.
import { createHiring } from "better-supabase/blocks/hiring";
const hiring = createHiring({ transport });
const job = await hiring
.saveJob(organizationId, {
title: "Backend engineer",
location: "Utrecht",
status: "open",
})
.orThrow();
const application = await hiring
.apply(job.id, { id: personId }, { source: "referral" })
.orThrow();
await hiring
.scheduleInterview(application.id, {
startsAt,
endsAt,
interviewerIds: [engineerId],
kind: "technical",
})
.orThrow();Applications
apply(jobId, candidate, { source, ownerId }) adds a candidate to an open job (HIRING_JOB_NOT_OPEN); applying again returns the same application. The candidate must be in the organization and readable by the caller (HIRING_CANDIDATE_FORBIDDEN). Move it between stages with a stage update or rank on a board, as any pipeline record.
hire(id), reject(id, reason) and withdraw(id) move the application to its job pipeline's first stage of that category. Hiring stamps hiredAt and records application.hired with the candidate: call employees.fromApplication from that event, which makes the employee once and links it with a created_from relation, or create your own from an after_application_hire hook in the same transaction.
Interviews and scorecards
scheduleInterview records an interview with its interviewers, who can then read it. score(id, { rating, recommendation, notes }) saves the caller's scorecard at the application's current stage: a rating from 1 to 4 and a recommendation from strong_no to strong_yes. Scoring again at the same stage replaces it. detail(id) returns the application with its scorecards, its interviews and the average rating.
Events
| Event | Data |
|---|---|
job.opened, job.closed | organizationId, jobId, title |
application.created, application.hired, application.rejected, application.retracted | organizationId, applicationId, subjectType, subjectId, jobId, candidateType, candidateId, reason |
scorecard.submitted, interview.scheduled | the same, with reviewerId and recommendation, or interviewId and startsAt |
Wire it to ReUI
The ReUI Recruiting Pipeline board reads its columns from the job's pipeline board and moves cards with rank.move("application", id, { group: stageId }). Show a card's match score from detail(id).averageRating, and its interview date from interviews.
Errors
| Hint | Cause |
|---|---|
HIRING_FORBIDDEN | the caller lacks hiring.manage |
HIRING_JOB_INVALID | a job needs a title and a status of draft, open or closed |
HIRING_JOB_NOT_FOUND | no such job the caller can change |
HIRING_JOB_NOT_OPEN | the job isn't open |
HIRING_CANDIDATE_FORBIDDEN | the candidate is unknown or unreadable |
HIRING_APPLICATION_NOT_FOUND | no application the caller can change or review |
HIRING_OUTCOME_INVALID | close as hired, rejected or withdrawn |
HIRING_STAGE_MISSING | the job's pipeline has no stage of that category |
HIRING_SCORECARD_INVALID | a rating from 1 to 4 and a recommendation |
HIRING_INTERVIEW_INVALID | an interview starts before it ends |
Last updated on
Targets
Goals per period for a member, a team or the organization, with progress the database computes for won deals, completed work items and paid invoices, or a value the app keeps.
Employees
Employees with or without an account, a manager, team and status, an org chart, offboarding, and private details and contracts behind employees.view_sensitive.