Unified.to
All articles

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:

  • id
  • name
  • description
  • is_active
  • languages
  • categories
  • media
  • instructor_ids

Content (LmsContent)

Represents individual learning items.

Key fields:

  • id
  • name
  • description
  • course_ids
  • collection_ids
  • duration_minutes
  • sort_order
  • languages
  • localizations
  • skills
  • is_active

Content may belong to multiple courses and multiple collections, depending on provider support.

Collection (LmsCollection)

Represents module or grouping hierarchy.

Key fields:

  • id
  • name
  • parent_id
  • is_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_gte support varies by provider.
  • Where unsupported, perform periodic full scans.
  • Reconcile by id and updated_at.

Repeat for:

  • Content
  • Collections
  • Classes (if used)

Step 6: Determine Write Capability

Before propagating content to another LMS:

  1. Check provider-level writable field support.
  2. Confirm whether:
    • lms_course_create / update are supported
    • lms_content_create / update are supported
    • lms_collection_create / update are 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)
  1. Ingest source LMS structure
  2. Normalize to canonical model
  3. Detect structural deltas via polling
  4. Evaluate destination write capability
  5. Propagate only supported fields
  6. 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

→ Book a demo

All articles