Activity logs provide a complete audit trail of all actions performed on candidate applications, helping you maintain transparency and track the hiring process.
Overview
Every significant action in your hiring workflow is automatically logged, including:
Stage movements
Evaluation submissions
Status changes
Communication records
And more
This creates a comprehensive timeline of each candidate’s journey through your hiring pipeline.
ActivityLog model
The core structure for tracking activities:
model ActivityLog {
id String @id @default ( cuid ())
action String // e.g. "MOVED_STAGE", "ADDED_EVALUATION"
metadata Json ? // Stores old/new values
actorId String ?
actor User ? @relation ( fields : [ actorId ], references : [ id ], onDelete : SetNull )
applicantId String
applicant Applicant @relation ( fields : [ applicantId ], references : [ id ], onDelete : Cascade )
createdAt DateTime @default ( now ())
@@index ( [ applicantId ] )
@@map ( "activity_log" )
}
Common action types
MOVED_STAGE Candidate moved between pipeline stages
ADDED_EVALUATION Interviewer submitted an evaluation
STATUS_CHANGED Application status updated
CREATED_APPLICATION New application submitted
Creating activity logs
Activity logs are typically created automatically by server functions:
Stage movement
await prisma . activityLog . create ({
data: {
action: "MOVED_STAGE" ,
actorId: session . user . id ,
applicantId: applicantId ,
metadata: {
from: "Phone Screen" ,
to: "Technical Interview"
}
}
});
Evaluation submission
await prisma . activityLog . create ({
data: {
action: "ADDED_EVALUATION" ,
actorId: session . user . id ,
applicantId: applicantId ,
metadata: {
stage: "Technical Interview" ,
score: 4
}
}
});
The metadata JSON field stores contextual information about each action. This flexible structure allows you to capture:
Previous and new values for changes
Scores and ratings
Stage names
Additional context specific to the action
// Example metadata structures
const stageChangeMetadata = {
from: "Initial Review" ,
to: "Phone Screen"
};
const evaluationMetadata = {
stage: "Technical Interview" ,
score: 5 ,
interviewer: "Jane Smith"
};
const statusChangeMetadata = {
from: "UNDERREVIEW" ,
to: "INTERVIEW"
};
Viewing activity history
Retrieve the complete activity log for a candidate:
const applicant = await prisma . applicant . findUnique ({
where: { id: applicantId },
include: {
activityLogs: {
include: {
actor: {
select: {
id: true ,
name: true ,
email: true ,
image: true
}
}
},
orderBy: { createdAt: "desc" }
}
}
});
Actor tracking
The actorId field records who performed each action. This is nullable because:
Some actions are system-generated
The user who performed the action may have been deleted
Automated processes don’t have a user actor
// With actor (manual action)
{
action : "MOVED_STAGE" ,
actorId : "user-123" ,
actor : { name : "John Doe" }
}
// Without actor (automated action)
{
action : "CREATED_APPLICATION" ,
actorId : null ,
actor : null
}
When a user is deleted, the actorId is set to null but the activity log entry remains, preserving the audit trail.
Complete applicant history
Get a comprehensive view including logs, communications, and evaluations:
const history = await prisma . applicant . findUnique ({
where: { id: applicantId },
include: {
activityLogs: {
include: { actor: true },
orderBy: { createdAt: "desc" }
},
communications: {
orderBy: { createdAt: "desc" }
},
evaluations: {
include: { stage: true , interviewer: true },
orderBy: { createdAt: "desc" }
}
}
});
Filtering logs by action type
Find specific types of activities:
// Get all stage movements
const stageMovements = await prisma . activityLog . findMany ({
where: {
applicantId ,
action: "MOVED_STAGE"
},
orderBy: { createdAt: "asc" }
});
// Get all evaluations added
const evaluationLogs = await prisma . activityLog . findMany ({
where: {
applicantId ,
action: "ADDED_EVALUATION"
},
include: { actor: true }
});
Time-based queries
Track activities within specific time periods:
const recentActivity = await prisma . activityLog . findMany ({
where: {
applicantId ,
createdAt: {
gte: new Date ( Date . now () - 7 * 24 * 60 * 60 * 1000 ) // Last 7 days
}
},
include: { actor: true },
orderBy: { createdAt: "desc" }
});
Activity logs are automatically deleted when the associated applicant is removed due to cascade deletion.
Best practices
Log all significant actions : Ensure every important change creates an activity log entry
Use descriptive action names : Follow a consistent naming convention (VERB_NOUN)
Include relevant metadata : Store enough context to understand what changed
Display logs to users : Show hiring managers the complete candidate timeline
Don’t delete logs : Activity logs are your audit trail—preserve them