Skip to main content
Version: Latest
Technical ManualLooking for step-by-step instructions? In the User Manual: Skills and Certifications

Skills

The Skills module keeps one skill catalog for the site. Contact Center routing and workforce scheduling read the same skills, so a person's skills are set once. It adds:

  • Skills: a name, a category, a description and an optional required certification.
  • Skill assignments: a person holds a skill at a proficiency from 1 to 5, optionally between two dates. A person is an Orchard Core user, or a workforce worker who may have no user account.
  • Certifications (shown as Certification Types): a licence that expires after a number of months, with reminder days before expiry.
  • Certification records (certificates): one person's certificate, with its number, issue and expiry dates, a copy in the media library, and a status (Pending, Valid, Expired, Revoked).
  • A daily check that raises reminders, marks certificates expired, and tells other modules about assignments that start or end that day.

The admin screens are described in the User Manual: Skills and Certifications.

Feature IDNameDepends on
CrestApps.OrchardCore.SkillsSkillsCrestApps.OrchardCore.Users, CrestApps.OrchardCore.Resources

Projects: CrestApps.OrchardCore.Skills.Abstractions (models and contracts), CrestApps.OrchardCore.Skills.Core (stores, indexes, services), CrestApps.OrchardCore.Skills (the module).

Storage​

Every record is a YesSql document in the Skills collection: Skill, SkillAssignment, Certification, CertificationRecord, and one SkillsDailyCheckpoint that remembers the last day the daily check ran. Day columns in the indexes are stored as yyyyMMdd numbers, so a day comparison never shifts with a time zone.

IndexColumns
SkillIndexItemId, Name, NormalizedName, Category, Enabled
CertificationIndexItemId, Name, NormalizedName, Enabled
SkillAssignmentIndexItemId, SubjectKindName, SubjectId, SkillId, Proficiency, EffectiveFrom, EffectiveTo (and the legacy number column SubjectKind)
CertificationRecordIndexItemId, SubjectKindName, SubjectId, SubjectName, CertificationId, Number, Status, IssuedOn, ExpiresOn (and the legacy number column SubjectKind)

Names are unique without regard to case or surrounding spaces (NormalizedName). A person holds each skill at most once.

Services​

ContractWhat it does
ISkillManagerThe skill catalog (ICatalogManager<Skill>): validate, create, update, delete, FindByNameAsync, GetEnabledAsync, SearchAsync.
ICertificationManagerThe certification catalog, with the same members.
ISkillAssignmentServiceGetAssignmentsAsync (every assignment of a person), GetEffectiveAsync (the ones that count on a day: inside their dates, the skill enabled, and the required certification valid that day, including skills held under another subject that names the same person), SetAsync (replace a person's set), AssignAsync, RemoveAsync, FindHoldersAsync (who holds a skill at a proficiency on a day) and HoldsAsync.
ICertificationRecordServiceSaveAsync (works out the expiry date from the certification's validity when a record has an issue date and no expiry date, and refuses a valid record without its copy when the certification requires one), VerifyAsync, RevokeAsync, DeleteAsync, GetForSubjectAsync, IsValidOnAsync and PageAsync.

A person is a SkillSubject: SkillSubject.User(userId) or SkillSubject.Worker(workerId). The kind is an open string: SkillSubjectKind.User and SkillSubjectKind.Worker are the well-known kinds, and a module adds its own with new SkillSubject("MyKind", id) (and an ISkillSubjectLinkProvider that links it to the user or worker it names) without changing Skills. Records saved while the kind was a number are still read (SkillSubjectKindJsonConverter reads 0 as User and 1 as Worker), and the Skills migration adds the SubjectKindName index column and fills it in from the number, so nothing is dropped. Until that re-index has reached a row, the queries match it by the number column (SubjectKind: 0 for a user, 1 for a worker), so an older assignment or certification is never hidden and assigning the same skill again updates it rather than adding a second one.

var subject = SkillSubject.User(userId);

var result = await assignmentService.AssignAsync(subject, new SkillAssignmentInput
{
SkillId = spanish.ItemId,
Proficiency = 4,
EffectiveFrom = new DateOnly(2026, 12, 1),
}, actor: "hr-import");

var holders = await assignmentService.FindHoldersAsync(spanish.ItemId, minimumProficiency: 3, today, SkillSubjectKind.Worker);

Extension points​

ContractWhen it runsUse it for
ISkillSubjectLinkProviderWhenever a person's effective skills are read (GetEffectiveAsync, HoldsAsync).Telling Skills which other subjects name the same person. Workforce links a worker profile and its user, so a skill Contact Center gives an agent's user counts for the worker in scheduling, coverage and Dispatch matching. When both hold a skill, the higher proficiency counts.
ISkillAssignmentHandlerIn the request, after a person's skills changed and before the session commits; also once a day for each person whose assignment starts or ends that day (EffectiveDatesOnly).Keeping a copy of a person's skills in step, as Contact Center does for agents.
ICertificationExpiryObserverFrom the daily check: ExpiringAsync once per reminder offset and expiry date, ExpiredAsync once when a certificate is marked expired.Notifications. A failing observer is logged and doesn't stop the check.
ICatalogEntryHandler<Skill>, ICatalogEntryHandler<Certification>The catalog managers' lifecycle.Reacting to catalog changes.
SkillSubjectSkillsDisplayDriverBase<TModel> (in CrestApps.OrchardCore.Skills.Core)When the editor of a record that holds skills is built and updated.Adding a Skills section to that editor: one proficiency choice per enabled skill, shown only to users with Assign skills. Derive a small driver that says who holds the skills (GetSubject, an empty subject hides the section), names the section's shape (ShapeType, whose view takes a SubjectSkillsViewModel) and may move it (Location, Content:10 by default), and register it with services.AddDisplayDriver<TModel, TDriver>(). The assignments are written while the editor is updated, so the editor cancels the session when the record isn't saved. Skills uses it for the user editor (UserSkills_Edit), and Workforce for the worker editor (WorkerSkills_Edit).

Daily check​

The Skills and certificates daily check background task wakes every hour and runs once per day in the site's time zone:

  1. Valid certificates whose expiry date is within the largest reminder offset are read. When a certificate reaches a reminder offset, every offset already reached is marked raised and only the nearest is announced, so a certificate entered a week before it expires raises one reminder rather than one per offset. When the expiry date moves (the certificate was renewed), its reminders start again.
  2. The day after a certificate's expiry date, its status becomes Expired.
  3. People whose assignment starts that day or ended the day before are passed to the ISkillAssignmentHandlers.

Workflows​

With Workflows on, the module adds Certificate Expiring (DaysBefore in the input), Certificate Expired, Skills Changed and the Assign Skill task. The events start after the change is committed. Certificate events can be limited to some certifications; their input holds CertificationRecordId, CertificationId, CertificationName, SubjectKind, SubjectId, SubjectName, Number and ExpiresOn. Skills Changed holds SubjectKind, SubjectId, SkillIds, ChangedSkillIds, RemovedSkillIds, EffectiveDatesOnly and Actor.

Recipes and deployment​

The Skills step imports certifications and skills (by id, or by name when the id is new), then assignments and certificates. A skill can name its certification with RequiredCertificationName and an assignment its skill with skillName, so a recipe written for one site works on another.

{
"steps": [
{
"name": "Skills",
"Certifications": [
{ "name": "Gas safety", "issuer": "Gas Board", "validityMonths": 12, "reminderDays": [60, 30, 7], "requiresDocument": true }
],
"Skills": [
{ "name": "Spanish", "category": "Languages" },
{ "name": "Boiler servicing", "category": "Heating", "RequiredCertificationName": "Gas safety" }
],
"Assignments": [
{ "subjectKind": "User", "subjectId": "4x2kf6wqmq7a9y3p0v1d8hn5bt", "skillName": "Spanish", "proficiency": 4 }
],
"CertificationRecords": [
{ "subjectKind": "User", "subjectId": "4x2kf6wqmq7a9y3p0v1d8hn5bt", "subjectName": "John Smith", "certificationName": "Gas safety", "issuedOn": "2026-03-15", "status": "Valid" }
]
}
]
}

The Skills and certifications deployment step exports every skill and certification, and optionally the assignments and certificates.

Permissions​

PermissionAllows
ManageSkills (Manage skills and certifications)The skill and certification lists. Implies the two below.
AssignSkills (Assign skills and record certifications)The Skills section of the user editor, and adding, checking and withdrawing certificates.
ViewSkills (View skills and certifications)The certificate list.

Contact Center​

Contact Center keeps its skills in this catalog: the Contact Center Agents feature depends on Skills, so turning Contact Center on turns Skills on. No migration is needed.

  • Skills: with the Work Distribution (Queues) feature on, the Contact Center skill store (IContactCenterSkillStore) reads and writes the shared catalog. The first time it is read, skills Contact Center kept in its own store before are copied into the catalog with the same ids, so queues and agents that name them keep working. A skill whose name the catalog already has is left out, because Contact Center names skills by their name. Nothing is copied back: the old store is only read for that first copy. A change to a catalog skill clears Contact Center's cached skills.
  • Screens: skills are edited on the Skills module's pages (Admin/skills). Contact Center's menu item Skills opens them, and its old addresses (Admin/contact-center/skills) redirect there. A role with Manage Contact Center skills may also manage and assign the shared skills (ContactCenterSkillsAuthorizationHandler), so roles set up before keep their access.
  • Agents: with the Agents feature on, an agent's routing proficiencies (AgentProfile.SkillProficiencies, keyed by skill name) and the user's assignments stay in step both ways. Routing still reads the agent profile, so it does no extra queries. A fingerprint of the proficiencies on the profile means a save that didn't change them (most saves are presence changes) touches nothing. A save that did change them is written to the catalog after it is committed, in a scope of its own (a deferred task), never inside the agent's save: a failed catalog query cancels the whole YesSql session, so a catalog that can't be read, for example because the Skills migration timed out, would otherwise throw away every agent state change. A sync that fails is logged and tried again at the agent's next save. A new agent with no proficiencies takes the user's shared skills the same way, just after it is created. A Contact Center skill name that isn't in the catalog stays only on the agent. The skills are stored on the agent's user; a Workforce worker profile linked to the same user holds them too. The Contact Center shared skills backfill background task pushes the agents saved before Skills was turned on, 100 every five minutes, until all are done.