A common integration pattern: synchronize users from an external HR system to Pluvo.
Step 1: Create or update the user
mutation SyncUser($email: String!, $name: String!, $ref: String!) {
updateOrCreateUser(
email: $email
sendInvite: false
user: {
name: $name
ref: $ref
roles: [PARTICIPANT]
language: NL
}
) {
user { id ref }
created
}
}
Step 2: Set custom fields (department, etc.)
mutation SetUserFields($userRef: String!, $department: String!) {
updateExtraCategoryValues(
contentType: USER
modelRef: $userRef
values: [
{ categoryRef: "department", stringValue: $department }
]
) {
extraCategoryValues {
... on ExtraCategoryStringValue { stringValue }
}
}
}
Step 3: Add to group
mutation AddToGroup($groupRef: String!, $userRef: String!) {
addUsersToGroup(
groupRef: $groupRef
userRefs: [$userRef]
) {
addedIds
alreadyAddedIds
}
}
mutation ArchiveUser($ref: String!) {
updateOrCreateUser(
ref: $ref
user: { archived: true }
) {
user { id archived }
}
}
query GroupProgress($groupId: UUID!, $cursor: String) {
portfolioReport(
userGroupIds: [$groupId]
relatedObjectTypes: [TRAINING]
first: 50
after: $cursor
) {
edges {
node {
user { id ref name email }
title
progress
score
completionDate
timeSpent { days seconds }
}
}
pageInfo { hasNextPage endCursor }
count
}
}
Loop through all pages (see Pagination guide) to get the complete report.
Automate enrollment when a user joins a specific group.
Step 1: Listen for GROUP_UPDATED webhook (or run on schedule)
Step 2: Get group members
query GroupMembers($groupRef: String!) {
group(ref: $groupRef) {
users(first: 50) {
edges {
node { id ref }
}
}
}
}
Step 3: Enroll all members
mutation EnrollGroup($eventRef: String!, $userRefs: [String!]!) {
createOfferEventEnrollments(
offerEventRef: $eventRef
userRefs: $userRefs
sendMail: false
) {
id
user { ref }
status
}
}
Already-enrolled users are handled gracefully — they won't be duplicated.
Track when users earn certificates, e.g. for compliance reporting.
Option A: Webhook-based (real-time)
Listen for the EVENT_CERTIFICATE_ACHIEVED webhook:
{
"event": "EVENT_CERTIFICATE_ACHIEVED",
"user": { "id": "...", "name": "...", "ref": "..." },
"title": "Safety Certificate",
"training": { "id": "...", "title": "..." },
"creationDate": "2024-03-15",
"expiryDate": "2025-03-15"
}
Option B: Query-based (batch)
query RecentCertificates($from: Date!, $to: Date!) {
portfolioReport(
relatedObjectTypes: [CERTIFICATE]
completionDateFrom: $from
completionDateTo: $to
first: 50
) {
edges {
node {
title
user { ref name email }
completionDate
endDate
}
}
count
}
}
Step 1: Get group members
query {
group(ref: "all-employees") {
users(first: 50) {
edges {
node { ref }
}
}
}
}
Step 2: Add all to training
mutation {
addUsersToTraining(
trainingRef: "safety-2024"
userRefs: ["EMP-001", "EMP-002", "..."]
sendInvites: true
role: PARTICIPANT
) {
addedRefs
alreadyAddedRefs
failedRefs
}
}
query InactiveUsers($before: DateTime!, $cursor: String) {
paginatedUsers(
archived: false
lastLogin_Lt: $before
first: 50
after: $cursor
) {
edges {
node {
id
ref
name
email
lastLogin
}
}
pageInfo { hasNextPage endCursor }
count
}
}
Variables: { "before": "2024-04-01T00:00:00Z" }
ref everywhere — it's your anchor between systems. Set it on users, groups, trainings, offers, and events.updateOrCreateUser over separate create/update logic — it handles both cases.sendInvite: false / sendMail: false during bulk operations to avoid notification storms.