How to Sync Course Content Between LMS Platforms with Unified's LMS API
February 19, 2026
Syncing course content between LMS platforms is harder than it sounds.
Every LMS models courses and content differently. Some expose full course catalogs and lesson metadata. Others expose only partial structure. Some support module hierarchies. Others flatten everything under a course. And while unified schemas may suggest writable fields, provider-level capabilities often limit what can actually be created or updated.
When teams build 'content sync' logic assuming universal write support, the result is brittle integrations and inconsistent replication.
Unified's LMS API does not invent structure or simulate unsupported writes. It exposes course and content data exactly as the underlying LMS provides it. Syncing course content becomes a disciplined process of:
- Reading and normalizing structure
- Detecting changes via polling
- Propagating updates only where provider write support exists
- Falling back to mirror-only mode when it does not
This guide shows how to sync course content safely and accurately using Unified's LMS API.
What 'Sync Course Content' Means Here
In this context, syncing content means:
- Ingesting course catalogs and content structures from LMS platforms
- Maintaining a canonical internal representation
- Detecting structural changes over time
- Propagating updates to destination LMS platforms where supported
It does not mean:
- Guaranteed cross-LMS replication
- Universal module hierarchy writes
- Event-driven real-time structural updates
Unified preserves provider capabilities instead of abstracting them away.
The Unified LMS Content Model
Unified exposes the following core objects relevant to content syncing:
Course (LmsCourse)
Represents a course catalog entry.
Key fields:
idnamedescriptionis_activelanguagescategoriesmediainstructor_ids
Content (LmsContent)
Represents individual learning items.
Key fields:
idnamedescriptioncourse_idscollection_idsduration_minutessort_orderlanguageslocalizationsskillsis_active
Content may belong to multiple courses and multiple collections, depending on provider support.
Collection (LmsCollection)
Represents module or grouping hierarchy.
Key fields:
idnameparent_idis_active
Collections are optional and provider-dependent.
Class (LmsClass)
Represents sections or sessions of a course.
Optional structural layer depending on provider.
Step 1: Ingest the Source LMS Catalog
Start by fetching courses.
async function fetchCourses(connectionId: string) {
return await sdk.lms.listLmsCourses({
connectionId,
limit: 100,
sort: 'updated_at',
order: 'asc',
fields: 'id,name,description,is_active,updated_at',
});
}
Step 2: Fetch Course Content
Fetch content items linked to each course.
async function fetchCourseContent(connectionId: string, courseId: string) {
return await sdk.lms.listLmsContents({
connectionId,
course_id: courseId,
sort: 'sort_order',
order: 'asc',
fields: 'id,name,description,collection_ids,duration_minutes,sort_order,is_active',
});
}
Not all providers support sort_order or collection_ids. Always build structure dynamically.
Step 3: Fetch Module Hierarchy (If Supported)
async function fetchCollections(connectionId: string, courseId: string) {
return await sdk.lms.listLmsCollections({
connectionId,
course_id: courseId,
fields: 'id,name,parent_id,is_active',
});
}
Reconstruct module trees using parent_id.
If Collections are not supported:
- Treat content as flat under Course.
Step 4: Build a Canonical Content Tree
Combine Course, Collection, and Content objects into an internal model.
function buildContentTree(course, collections, contents) {
const modules = buildCollectionTree(collections);
const contentByCollection = {};
contents.forEach(c => {
(c.collection_ids ?? []).forEach(colId => {
if (!contentByCollection[colId]) contentByCollection[colId] = [];
contentByCollection[colId].push(c);
});
});
return { course, modules, contentByCollection };
}
Fallback strategy:
- If no collections → attach contents directly to course.
Step 5: Detect Structural Changes
LMS integrations do not provide webhook notifications for structural changes.
Use polling:
await sdk.lms.listLmsCourses({
connectionId,
updated_gte: lastSync,
});
Important:
updated_gtesupport varies by provider.- Where unsupported, perform periodic full scans.
- Reconcile by
idandupdated_at.
Repeat for:
- Content
- Collections
- Classes (if used)
Step 6: Determine Write Capability
Before propagating content to another LMS:
- Check provider-level writable field support.
- Confirm whether:
lms_course_create/updateare supportedlms_content_create/updateare supportedlms_collection_create/updateare supported
Many LMS providers:
- Allow limited Course updates (e.g.,
is_active,description) - Do not support deep content or module creation
- Do not support structural writes
Only propagate content when write support exists. Otherwise operate in mirror-only mode.
Mirror-Only Mode (Safe Default)
For providers without write support:
- Sync source LMS structure into your internal system
- Use your system as a unified catalog layer
- Do not attempt to replicate content into the destination LMS
This avoids 501 Not Implemented responses and inconsistent state.
Controlled Propagation Mode (When Supported)
Where provider write support exists:
- Create missing Courses in destination LMS
- Create or update Content items
- Rebuild module relationships (if writable)
- Respect provider field constraints
Always:
- Validate writable fields via Supported Integrations matrix
- Handle 403 (scope errors) and 501 (unsupported operations)
Recommended Sync Architecture
- Ingest source LMS structure
- Normalize to canonical model
- Detect structural deltas via polling
- Evaluate destination write capability
- Propagate only supported fields
- Fallback to mirror-only when unsupported
This architecture works across heterogeneous LMS ecosystems.
Provider Variability Reality
Across LMS providers:
- Course read support is common
- Content read support varies
- Collection/module support is inconsistent
- Write support is limited and provider-specific
- No webhook support for structure changes
- Incremental filtering (
updated_gte) varies by provider
Design for variability.
Closing Thoughts
Syncing course content across LMS platforms is not about forcing parity between systems. It is about respecting provider capabilities while maintaining a consistent internal representation.
Unified's LMS API gives you:
- A normalized content model
- Consistent parameter semantics
- Polling-based structural sync
- Explicit provider-level support boundaries
With a disciplined mirror-first approach and controlled propagation where supported, you can build reliable cross-LMS content workflows without overclaiming or relying on unsupported behavior. → Start your 30-day free trial