1 · When we create a class
One Course record per dated session. Set the logistics once; automation does the rest.
flowchart TD A([fa:fa-calendar-days New class scheduled]):::start --> B["Create a Course record
one Course = one dated session"]:::obj B --> C["Set Course properties once:
cohort code · dates · venue (TBA ok) · hotel · trainer · capacity"]:::obj C --> D["Associate registrants as Contacts
label = Registered (many-to-many)"]:::step D --> E{Venue confirmed?}:::dec E -- "still TBA" --> D E -- "TBA → real venue" --> F["fa:fa-gear Workflow: copy Course venue/date
→ all associated contacts"]:::wf F --> G["fa:fa-envelope Workflow: send 'Venue confirmed'
marketing email to all attendees"]:::email G --> H["Advance stage; mark Attended after the session"]:::step classDef start fill:#f2f3f4,stroke:#cdd3d9,color:#232b33; classDef obj fill:#FDEBE8,stroke:#EF4C32,color:#a3301c; classDef email fill:#FDEBE8,stroke:#EF4C32,color:#a3301c; classDef wf fill:#eef0f2,stroke:#6b7682,color:#3f4852; classDef step fill:#fff,stroke:#cdd3d9,color:#232b33; classDef dec fill:#fff5ee,stroke:#EB5825,color:#8a3d1e;
2 · Backfilling everyone's prior classes
Consolidate historical attendance into one file; HubSpot builds the Course records + associations in a single import.
flowchart TD S1["Source A — contact properties
course_name + date + venue
~101 contacts already tagged"]:::src --> M S2["Source B — static lists / CSV imports
older cohorts (TLG, CoP, etc.)"]:::src --> M M["Consolidate → one spreadsheet
1 row per attendee"]:::step --> K["Add a Cohort Code column
repeated for every attendee in that session"]:::obj K --> I["fa:fa-inbox Multi-object import (Courses + Contacts)
matched on Cohort Code + email"]:::obj I --> R["HubSpot dedupes Courses by Cohort Code
→ 1 Course per cohort, attendees associated"]:::email R --> DONE([fa:fa-circle-check History backfilled
no per-record clicking]):::done classDef src fill:#fff,stroke:#cdd3d9,color:#232b33; classDef step fill:#fff,stroke:#cdd3d9,color:#232b33; classDef obj fill:#FDEBE8,stroke:#EF4C32,color:#a3301c; classDef email fill:#FDEBE8,stroke:#EF4C32,color:#a3301c; classDef done fill:#eef6f3,stroke:#1d7a5f,color:#1d7a5f;
A single multi-object import — not a workflow
HubSpot can create Course records and associate them to Contacts in the same import. One row per attendee with a shared Cohort Code; rows sharing a code collapse into one Course with many attendees. I can pre-build Source A (the ~101 already-tagged contacts) straight from the API into this sheet.
3 · The Course object schema to build
The object ships with the defaults below (0 custom yet). Add these custom properties so a Course record holds a whole session's logistics. (Fixes today's typos along the way — e.g. hotel_recommendations.)
| Property to add | Type | Holds |
|---|---|---|
cohort_code | Single-line text | Stable key — e.g. TFM-2026-04-22-MN (dedup key for import & reporting) |
program_code | Dropdown | TFM · HCS · HIJ · HSF · AES · MTOP · CoP … |
session_start_date / session_end_date | Date picker (date only — HubSpot has no time-of-day on dates) | The session dates |
session_times / day_2_times | Dropdown (preset options) — mirrors your existing course_time | e.g. "9:00 AM – 4:00 PM" — keeps times consistent & filterable |
daily_schedule | Multi-line text | Agenda detail |
venue_name / venue_address | Text / Multi-line | Where it's held |
parking_info / hotel_recommendations | Multi-line text | Logistics (typo corrected) |
lead_trainer / lead_trainer_email | Single-line text | Primary trainer |
trainer_2 / trainer_2_email | Single-line text | Second trainer |
delivery_mode | Dropdown | In-person / Virtual / Hybrid |
Use the built-in defaults too: hs_course_name (session label), hs_enrollment_capacity (seats), hs_pipeline_stage, Owner.
4 · Associations & pipeline
| Setting | Value |
|---|---|
| Course → Contact association labels | Interested · Registered · Attended · No-show · Cancelled (many-to-many — a contact can join many courses) |
| Pipeline stages (ships as Open / Closed) | Planning → Registration Open → Confirmed → Running → Complete (+ Cancelled) |
5 · Order of operations
| # | Step | Where |
|---|---|---|
| 1 | Add the custom Course properties above (incl. cohort_code) | Settings → Properties → Course |
| 2 | Add the association labels (Registered / Attended / No-show) & set the pipeline stages | Settings → Objects → Courses |
| 3 | Build the backfill spreadsheet (auto-export Source A + gather Source B) | Spreadsheet |
| 4 | Import Courses + Contacts, matched on Cohort Code + email | CRM → Import |
| 5 | Build the two workflows: copy-down (Course venue → contacts) and announcement email | Automation → Workflows |
| 6 | Going forward: every new class starts as a Course record (Flow 1) | — |
6 · Why this is the right call — what the authorities say
This is the model HubSpot and its partners recommend for courses — and, importantly, the Course object is part of the Object Library, which activates on your current plan (it is not a fully-custom object that would require Enterprise).
"Most teams tracking training… are doing it wrong. They're logging course completions as Deal stages, tracking certifications in a custom Contact property with 47 dropdown options…" The fix — HubSpot's Course object — was "released in late 2024 as part of their Object Library."
The recommended model is one record per session with attendees associated — "For an event with multiple sessions, a Session… object is a must-have."
Associate contacts to the course with association labels — "Interested, Enrolled, Completed" — usable in segments, reporting and automation; many-to-many so one contact can join multiple courses.
To associate contacts with records on import, "import them both at the same time, with a common field column… and unique IDs… to prevent duplicates." Native import handles many-to-many awkwardly; tools like Insycle/Coefficient bulk-associate via email matching.