npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@openedx/sample-plugin

v1.3.1

Published

This directory contains a React component that demonstrates how to customize Open edX micro-frontends (MFEs) using the Frontend Plugin Framework. The plugin replaces the default course list in the learner dashboard with a custom implementation that includ

Readme

Frontend Plugin Implementation Guide

This directory contains a React component that demonstrates how to customize Open edX micro-frontends (MFEs) using the Frontend Plugin Framework. The plugin replaces the default course list in the learner dashboard with a custom implementation that includes course archiving functionality.

Table of Contents

Overview

This frontend plugin demonstrates Open edX MFE customization using the Frontend Plugin Framework to replace the course list component in the learner dashboard.

What this plugin provides:

  • Custom CourseList Component: Enhanced course display with archive functionality
  • Backend API Integration: Connects to the sample backend plugin APIs
  • Slot Replacement Pattern: Shows how to replace existing MFE components
  • State Management: React patterns for plugin development
  • Authentication Integration: Uses Open edX authentication system

Official Documentation:

Frontend Plugin Framework

What Are Plugin Slots?

A "frontend plugin slot" is an area of a web page that can be customized with different visual elements without forking the codebase. This allows site operators to customize MFEs using configuration files.

Key Concepts:

  • Slot: A predefined customization point in an MFE
  • Plugin: Custom code that fills or modifies a slot
  • Operations: Actions you can take on slots (Insert, Modify, Replace)

Plugin Operations

| Operation | What It Does | When To Use | |-----------|--------------|-------------| | Insert | Add new components before/after existing ones | Adding new features alongside existing ones | | Modify | Change properties of existing components | Tweaking existing functionality | | Replace | Completely replace existing components | Major customization (like this example) |

Discovering Available Slots

Slot Documentation: Available Frontend Plugin Slots

MFE-Specific Slots: Each MFE documents its slots in /src/plugin-slots/ directory:

CourseList Component Example

File: src/plugin.jsx

Component Structure

const CourseList = ({ courseListData }) => {
  const [archivedCourses, setArchivedCourses] = useState(new Set());
  const [loadingStates, setLoadingStates] = useState(new Map());

  // Component implementation...
};

Key Features

1. Slot Data Integration

The component receives courseListData from the learner dashboard slot:

// Safety check for slot data
if (!courseListData || !courseListData.visibleList) {
  return <div>Loading courses...</div>;
}

const courses = courseListData.visibleList;

Slot Props: Each slot provides specific data. For CourseListSlot, see the slot documentation.

2. Backend API Integration

useEffect(() => {
  const fetchArchivedCourses = async () => {
    const client = getAuthenticatedHttpClient();
    const lmsBaseUrl = getConfig().LMS_BASE_URL;

    const response = await client.get(
      `${lmsBaseUrl}/sample-plugin/api/v1/course-archive-status/`,
      { params: { is_archived: true } }
    );

    const archivedCourseIds = new Set(
      response.data.results.map((item) => item.course_id)
    );
    setArchivedCourses(archivedCourseIds);
  };

  fetchArchivedCourses();
}, []);

Key Patterns:

  • Authentication: getAuthenticatedHttpClient() handles Open edX auth
  • Configuration: getConfig().LMS_BASE_URL gets platform URLs
  • Error Handling: Try/catch blocks for API failures

3. Open edX UI Components

The plugin uses Paragon (Open edX's design system):

import {
  Card,
  Container,
  Row,
  Col,
  Badge,
  Collapsible,
  Button,
  Spinner,
  Dropdown,
  IconButton,
  Icon,
} from "@openedx/paragon";
import { Archive, Unarchive, MoreVert } from "@openedx/paragon/icons";

Why Paragon: Ensures consistent styling with the rest of Open edX interfaces.

Paragon Documentation: Paragon Design System

State Management

Archive Status Management

const [archivedCourses, setArchivedCourses] = useState(new Set());
const [loadingStates, setLoadingStates] = useState(new Map());

const handleArchiveToggle = async (courseId, isCurrentlyArchived) => {
  setLoadingStates((prev) => new Map(prev).set(courseId, true));

  try {
    // API calls to backend
    if (isCurrentlyArchived) {
      // Unarchive logic
    } else {
      // Archive logic
    }

    // Update local state
    setArchivedCourses((prev) => {
      const newSet = new Set(prev);
      isCurrentlyArchived ? newSet.delete(courseId) : newSet.add(courseId);
      return newSet;
    });
  } catch (error) {
    console.error("Archive operation failed:", error);
  } finally {
    setLoadingStates((prev) => {
      const newMap = new Map(prev);
      newMap.delete(courseId);
      return newMap;
    });
  }
};

Patterns Used:

  • Optimistic Updates: Update UI immediately, rollback on failure
  • Loading States: Track loading per course for better UX
  • Immutable Updates: Use functional setState for complex state

Slot Integration Patterns

CourseListSlot Integration

Target Slot: course_list_slot in learner dashboard

Configuration Pattern (for local development in env.config.jsx):

import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
import { CourseList } from '@openedx/sample-plugin';

const config = {
  pluginSlots: {
    course_list_slot: {
      keepDefault: false,  // Hide original component
      plugins: [
        {
          op: PLUGIN_OPERATIONS.Insert,
          widget: {
            id: 'custom_course_list',
            type: DIRECT_PLUGIN,
            priority: 60,
            RenderWidget: CourseList  // Your custom component
          },
        },
      ],
    },
  },
}

Plugin Configuration Options

| Option | Purpose | Values | |--------|---------|--------| | keepDefault | Show/hide original component | true, false | | op | Plugin operation type | Insert, Modify, Replace | | priority | Loading order | Higher numbers load later | | type | Plugin implementation type | DIRECT_PLUGIN, IFRAME_PLUGIN | | RenderWidget | Your React component | Component reference |

Slot Props and Data

Each slot provides specific props. For CourseListSlot:

const CourseList = ({
  courseListData,  // Course data from platform
  // Other props depend on the slot
}) => {
  // courseListData.visibleList - Array of course objects
  // courseListData.course - Course metadata
  // courseListData.courseRun - Course run information
};

Finding Slot Props: Check the slot's README in the MFE repository, or examine the slot implementation in /src/plugin-slots/.

API Integration

Authentication Patterns

Open edX Authentication:

import { getAuthenticatedHttpClient } from "@edx/frontend-platform/auth";

const client = getAuthenticatedHttpClient();
// Client automatically includes authentication headers

Configuration Access:

import { getConfig } from "@edx/frontend-platform";

const lmsBaseUrl = getConfig().LMS_BASE_URL;
const apiUrl = `${lmsBaseUrl}/sample-plugin/api/v1/course-archive-status/`;

Error Handling Best Practices

try {
  const response = await client.post(url, data);
  // Success handling
} catch (error) {
  console.error("API Error:", {
    status: error.response?.status,
    statusText: error.response?.statusText,
    data: error.response?.data,
    message: error.message,
  });

  // User feedback
  // Consider using toast notifications or error states
}

API Response Handling

// Handle paginated responses
const response = await client.get(url);
const items = response.data.results || [];  // DRF pagination format

// Handle different response formats
if (response.data && Array.isArray(response.data)) {
  // Direct array response
} else if (response.data.results) {
  // Paginated response
} else {
  // Single object response
}

Development Workflow

Prerequisites

  1. MFE Setup: Have a learner dashboard MFE running locally
  2. Backend Plugin: Install the backend plugin (see ../backend/README.md)
  3. Node.js: Version 16+ with npm or yarn

Local Development Setup

Step 1: Install Plugin Package

# In your MFE directory (e.g., frontend-app-learner-dashboard)
npm install /path/to/sample-plugin/frontend

Step 2: Create env.config.jsx

Create env.config.jsx in your MFE root (not committed to repo):

import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
import { CourseList } from '@openedx/sample-plugin';

const config = {
  pluginSlots: {
    course_list_slot: {
      keepDefault: false,
      plugins: [
        {
          op: PLUGIN_OPERATIONS.Insert,
          widget: {
            id: 'custom_course_list',
            type: DIRECT_PLUGIN,
            priority: 60,
            RenderWidget: CourseList
          },
        },
      ],
    },
  },
}

export default config;

Step 3: Create module.config.js

Create module.config.js for local development:

module.exports = {
  localModules: [
    {
      moduleName: '@openedx/sample-plugin',
      dir: '/path/to/sample-plugin/frontend'
    },
  ],
};

Purpose: Webpack uses your local plugin code instead of the installed package.

Step 4: Start Development

# In your MFE directory
npm ci
npm start

Development vs Production Configuration

Local Development:

  • Uses env.config.jsx for slot configuration
  • Uses module.config.js for local code loading
  • Hot reload for faster development

Production Deployment:

  • Configuration via Tutor plugins
  • Plugin installed as npm package
  • Optimized builds and caching

Testing Frontend Plugins

Unit Testing

// Example test structure
import { render, screen } from '@testing-library/react';
import { CourseList } from './plugin';

describe('CourseList Plugin', () => {
  test('renders course list with archive functionality', () => {
    const mockCourseData = {
      visibleList: [/* mock course data */]
    };

    render(<CourseList courseListData={mockCourseData} />);

    expect(screen.getByText('Archive')).toBeInTheDocument();
  });
});

Integration Testing

Test within the actual MFE environment:

  1. Set up MFE with plugin installed
  2. Create test courses in platform
  3. Verify plugin functionality
  4. Test API integration
  5. Check error handling

Deployment Considerations

Production Deployment with Tutor

Tutor Plugin Configuration (see ../tutor/README.md):

# In tutor plugin
PLUGIN_SLOTS.add_items([
    (
        "learner-dashboard",
        "custom_course_list",
        """
        {
          op: PLUGIN_OPERATIONS.Insert,
          type: DIRECT_PLUGIN,
          priority: 50,
          RenderWidget: CourseList
        }"""
    ),
])

Performance Considerations

Bundle Size:

  • Frontend plugins are included in MFE bundles
  • Minimize dependencies and use tree shaking
  • Consider lazy loading for large plugins

API Performance:

  • Implement proper caching strategies
  • Use pagination for large datasets
  • Optimize backend API response times

User Experience:

  • Show loading states during API calls
  • Handle errors gracefully
  • Provide offline fallback behavior

Browser Compatibility

  • Follow MFE browser support requirements
  • Test across different browsers
  • Use polyfills if needed for newer JS features

Customizing This Example

For Different Slots

  1. Identify Target Slot: Check available slots
  2. Study Slot Props: Examine slot documentation for available data
  3. Adapt Component: Modify component to work with slot-specific data
  4. Update Configuration: Change slot name in plugin configuration

Example - Adapting for Header Slot:

// Original CourseList component
const CourseList = ({ courseListData }) => { /* ... */ };

// Adapted for header slot
const CustomHeader = ({ logo, mainMenu, userMenu }) => {
  // Use header-specific props
  return (
    <Header logo={logo} mainMenu={mainMenu}>
      {/* Your customizations */}
    </Header>
  );
};

Adding New Features

Common Extension Patterns:

// Add new state
const [newFeatureData, setNewFeatureData] = useState([]);

// Add new API calls
useEffect(() => {
  const fetchNewFeatureData = async () => {
    // Your API integration
  };
}, []);

// Add new UI elements
return (
  <Container>
    {/* Existing course list */}
    {/* Your new feature */}
    <YourNewComponent data={newFeatureData} />
  </Container>
);

Component Composition

Reusable Components:

// Create reusable sub-components
const ArchiveButton = ({ courseId, isArchived, onToggle }) => (
  <Button onClick={() => onToggle(courseId, isArchived)}>
    {isArchived ? 'Unarchive' : 'Archive'}
  </Button>
);

// Use in main component
const CourseList = ({ courseListData }) => (
  <div>
    {courses.map(course => (
      <Card key={course.id}>
        {/* Course info */}
        <ArchiveButton
          courseId={course.id}
          isArchived={isArchived(course.id)}
          onToggle={handleArchiveToggle}
        />
      </Card>
    ))}
  </div>
);

Troubleshooting

Common Issues

Plugin Not Loading:

  • Check env.config.jsx slot name matches target slot
  • Verify plugin is installed (npm list @openedx/sample-plugin)
  • Ensure MFE supports the plugin framework version
  • Check browser console for JavaScript errors

Slot Data Issues:

  • Console.log slot props to understand data structure
  • Check if slot provides expected data (some slots may not provide certain props)
  • Verify slot exists in the MFE version you're using

API Integration Problems:

  • Verify backend plugin is installed and running
  • Check API URLs match backend configuration
  • Ensure CORS settings allow frontend-backend communication
  • Test API endpoints directly in browser/Postman

Styling Issues:

  • Use Paragon components for consistent styling
  • Check CSS specificity conflicts
  • Verify theme variables are available
  • Test across different screen sizes

Development Setup Issues:

  • Ensure module.config.js path is correct
  • Check that both env.config.jsx and module.config.js are in MFE root
  • Verify file permissions and syntax

Debugging Techniques

Console Debugging:

// Add debug logging
console.log("DEBUG: CourseList props:", { courseListData });
console.log("DEBUG: API response:", response.data);
console.log("DEBUG: Archive states:", Array.from(archivedCourses));

React Developer Tools:

  • Use React DevTools to inspect component state
  • Check component hierarchy and props
  • Monitor state changes during interactions

Network Debugging:

  • Use browser DevTools Network tab
  • Check API request/response details
  • Verify authentication headers are present

Getting Help

  1. Documentation: Start with official frontend plugin documentation
  2. MFE-Specific Help: Check individual MFE repositories for slot documentation
  3. Community: Open edX Slack #frontend-platform channel
  4. Issues: Report bugs in relevant MFE repositories or this sample repository

This frontend plugin demonstrates the power and flexibility of the Open edX Frontend Plugin Framework. By following these patterns, you can create rich customizations that integrate seamlessly with the Open edX ecosystem.