In Pluvo, the enrollment system is built around three connected entities:
Offer (the registration offering)
└── OfferEvent (a specific entry point, e.g. start date or cohort)
├── OfferEventDate (a scheduled session within the event)
├── OfferEventDate (another session)
└── Enrollments (users registered for this event)Offer is the overarching registration opportunity — think of it as a catalog entry. It has a title, description, and visibility settings, but users don't register for an Offer directly.
OfferEvent is what users actually enroll in. Each OfferEvent represents a specific instance — for example, "Starting Moment September" or "Online Cohort Q4". An OfferEvent has:
OfferEventDate entries with start/end timesOfferEventDate represents a single scheduled session within an event — a workshop day, an online meeting, etc. Each date has a start time, end time, optional location, and optional trainers.
Offer: "Project Management Training"
├── OfferEvent: "Spring Cohort 2024" (type: TRAINING)
│ ├── Date: March 15, 09:00–17:00 (Workshop Day 1)
│ ├── Date: March 22, 09:00–17:00 (Workshop Day 2)
│ └── Linked content: Training "PM Fundamentals"
└── OfferEvent: "Fall Cohort 2024" (type: TRAINING)
├── Date: October 10, 09:00–17:00
├── Date: October 17, 09:00–17:00
└── Linked content: Training "PM Fundamentals"hasDetailPagehasDetailPage: false (default) | hasDetailPage: true | |
|---|---|---|
| Intended for | Simple offers with one event | Offers with multiple events/cohorts |
| UI behavior | Shown as a single item in the catalog. Clicking opens the offer directly in a drawer. | Shown with a detail page listing all events. Users pick an event to enroll in. |
| Event titles | Event title is not prominently shown — the offer title is used. | Each event has its own visible title and subtitle. |
mutation {
createOffer(
offer: {
title: "Fire Safety E-learning"
ref: "offer-fire-safety"
hasDetailPage: false
}
) {
offer { id }
}
}Then add the event:
mutation {
createOfferEvent(
offerRef: "offer-fire-safety"
offerEvent: {
type: ELEARNING
title: "Fire Safety E-learning"
accessCourses: ["uuid-of-course"]
publish: true
}
) {
offerEvent { id title }
}
}mutation {
createOffer(
offer: {
title: "Project Management Program"
description: "Choose your preferred starting moment."
ref: "offer-pm"
hasDetailPage: true
}
) {
offer { id }
}
}query {
offer(id: "uuid-here") {
id
title
ref
description
isActive
offerEvents {
id
title
type
enrolledCount
spotsAvailable
dates {
startDate
endDate
}
}
}
}query {
offers {
id
title
ref
isActive
}
}Step 1: Create the offer
mutation {
createOffer(
offer: {
title: "Project Management Training"
description: "Register for our PM training program."
ref: "offer-pm-2024"
}
) {
offer {
id
title
ref
}
}
}Step 2: Add an event with dates
mutation {
createOfferEvent(
offerRef: "offer-pm-2024"
offerEvent: {
type: TRAINING
title: "Spring Cohort 2024"
ref: "pm-spring-2024"
accessTrainings: ["uuid-of-training"]
maxSubscriptions: 20
waitingList: true
dates: [
{
startDate: "2024-03-15T09:00:00Z"
endDate: "2024-03-15T17:00:00Z"
},
{
startDate: "2024-03-22T09:00:00Z"
endDate: "2024-03-22T17:00:00Z"
}
]
}
) {
offerEvent {
id
title
ref
dates { id startDate endDate }
}
}
}Step 3: Enroll users (see Enrollments page)
mutation {
createOfferEventEnrollments(
offerEventRef: "pm-spring-2024"
userRefs: ["EMP-001", "EMP-002"]
sendMail: true
) {
id
user { name }
status
}
}mutation {
updateOffer(
ref: "offer-pm-2024"
offer: {
title: "Updated title"
}
) {
offer { id title }
}
}Unlike most entities, offers can be permanently deleted (not just archived).
mutation {
deleteOffer(id: "uuid-here") {
ok
}
}type Offer {
id: ID!
ref: String
title: String!
description: String
isActive: Boolean
archivedAt: DateTime
createdAt: DateTime
updatedAt: DateTime
offerEvents: [OfferEvent!]
type: OfferType
hasDetailPage: Boolean
}type OfferEvent {
id: ID!
ref: String
title: String
type: OfferEventType!
dates: [OfferEventDate!]
maxSubscriptions: Int
minSubscriptions: Int
enrolledCount: Int
spotsAvailable: Int
waitingList: Boolean
closingDate: DateTime
price: Decimal
cancelled: Boolean
publish: Boolean
trainers: [User!]
accessTrainings: [Training!]
accessCourses: [Course!]
certificateId: UUID
scoreThreshold: Float
}type OfferEventDate {
id: ID!
ref: String
startDate: DateTime!
endDate: DateTime!
location: String
webinarLink: String
trainers: [User!]
}enum OfferEventType {
MEETING
TRAINING
ELEARNING
COURSE
SCORM
VIDEO
}offer(id: UUID, ref: String): Offer
offers(q: String, types: [OfferType!]): [Offer!]!createOffer(offer: CreateOfferInput!): CreateOffer
updateOffer(id: UUID, ref: String, offer: UpdateOfferInput!): UpdateOffer
deleteOffer(id: UUID!): DeleteOffer
copyOffer(id: UUID!): CopyOffer
createOfferEvent(offerId: UUID, offerRef: String, offerEvent: CreateOfferEventInput!): CreateOfferEvent
updateOfferEvent(id: UUID, ref: String, offerEvent: UpdateOfferEventInput!): UpdateOfferEvent
deleteOfferEvent(id: UUID!): DeleteOfferEvent
copyOfferEvent(id: UUID!): CopyOfferEventOffer is not directly registrable — enrollment happens through OfferEvents.OfferEvent.type is required when creating — it determines the kind of content.accessTrainings, accessCourses, etc.OfferEventDate) are created inline via the dates field on createOfferEvent.id and ref work for identification in all queries and mutations.copyOffer to duplicate an offer with all its events — useful for recurring programs.deleteOffer and deleteOfferEvent permanently remove the entity.