At Daily, we're really excited about remote educational initiatives. Video and audio APIs like ours can help to make more learning opportunities available to anyone with an Internet connection. Developers building with Daily will often come to us with questions or ideas specifically centered around e-learning and we've learned along the way what a lot of the common themes have been for them.

In this post, we'll cover some usages of Daily in the context of educational applications and show you a virtual class demo we've created using NextJS. This post will give you a basic idea of how you can design a purpose-built e-learning application using our WebRTC API.

Whether you are hosting educational lectures or hands-on workshops, Daily’s APIs can support all sorts of educational use cases. Some popular features often brought up in educational contexts include:

  • Moderation
  • Transcription
  • Whiteboarding
  • Raise Your Hand
  • Polls

Let’s have a quick look at how we’ve approached these features in our virtual class demo, which you can try for yourself in our deployed application. The source code for the demo can be found in our GitHub repository.

At the end of this post, we'll provide some links to other resources we have which might come in handy for an e-learning application.

Video call participant tiles with transcription and teacher call controls at the bottom
Virtual class demo teacher view

Running the demo locally

If you’d like to run the demo locally, you will need a free Daily account and a Daily room

Next, clone the demo repository:

git clone
cd virtual-class-demo

Set up your local environment settings by running the following from the project root:

mv env.example .env.local

You can find your Daily API key and domain in your Daily dashboard.
Finally, install the dependencies and run the demo as follows:

yarn dev

Once you launch the demo, you’ll be directed to create your own virtual classroom. The demo then allows participants to join as either a student or a teacher, and to invite other users to the room. Feel free to try both the student and teacher flows to get an idea of the experience from both perspectives!

A "Join your class" modal with buttons to join as a teacher or a student
Joining the class as a teacher or student

With that, let’s dig into our highlighted e-learning features.


A virtual classroom can benefit from moderation features which the instructor or assistants can use. In our Daily virtual class demo, teachers can:

  • Allow students to unmute their mics to talk
  • Mute all microphones
  • Mute all cameras

Let’s take a quick look at how we’ve implemented each of these features.

Allowing students to unmute their mics

A teacher can toggle whether students are allowed to unmute their microphones via a button in the top right hand side of the call window.

Toggling "Allow students to talk" option.

The button is configured as follows in our Header.js file:

  label="Allow students to talk"

When clicked, the setAllowToTalk() method is called, which saves the setting in shared application state:

  const setAllowToTalk = useCallback(() => {
    setSharedState(sharedState => {
      return {
        allowToTalk: !sharedState.allowToTalk,
  }, [setSharedState]);

The application can then retrieve this state on demand via our isAllowedToTalk const:

const isAllowedToTalk = useMemo(() => {
    return sharedState.allowToTalk || !!localParticipant?.owner;
  }, [localParticipant?.owner, sharedState.allowToTalk]);

When “allow to talk” is disabled, all participants are muted via an effect hook:

 useEffect(() => {
    if (sharedState.allowToTalk) return;

    // Mute all the participants when "allow to talk" is disabled.
    if (!isAllowedToTalk) {
  }, [daily, isAllowedToTalk, sharedState.allowToTalk]);

Above, we use Daily's setLocalAudio() call object instance method to disable the local participant's microphone.

The allowToTalk state then informs whether students are able to unmute themselves or not:

        onClick={() => toggleMic(isMicMuted)}

And that’s it! Teachers can now control whether students partaking in the class are allowed to unmute or not.

Next, let’s take a look at how a teacher can mute all of their students’ microphones and cameras on command.

Muting all microphones and cameras

A teacher has a “Mute all mics” and “Mute all cams” button available at the top of the “People” view on the side of the application:

Teacher clicking on "Mute all mics" and "Mute all cams" button
Muting microphones and cameras

These are implemented in PeopleAside.js as follows:

        {isOwner && (
          <div className="owner-actions">
              Mute all mics
              Mute all cams

Above, the mute buttons are displayed if the local participant is an “owner” of the meeting (defined by whether they joined with an owner meeting token).

When clicked, each of these buttons call handleMuteAllAudio or handleMuteAllVideo respectively:

const handleMuteAllAudio = () => muteAll('audio');
const handleMuteAllVideo = () => muteAll('video');

As we can see, both of these functions are aliases for a common muteAll() function taking ”audio” or ”video” as a parameter. Let’s go through it now:

  const muteAll = useCallback(
    deviceType => {
      if (!deviceType) {
        console.error('missing device type to mute');
      const lcDeviceType = deviceType.toLowerCase();
      if (lcDeviceType !== 'audio' && lcDeviceType !== 'video') {
          `failed to recognize device type to mute (${deviceType})`,
      let updatedParticipantList = {};
      // Accommodate muting mics and cameras
      const newSetting =
        deviceType === 'video' ? { setVideo: false } : { setAudio: false };
      for (let id in callObject.participants()) {
        // Do not update the local participant's device (aka the teacher)
        if (id === 'local') continue;

        updatedParticipantList[id] = newSetting;

      // Update all participants at once

Above, we validate the input parameter (which needs to be either ”audio” or ”video”). The parameter informs the new setting we will apply for all students: either {setVideo: false} or {setAudio: false}.

Then, we iterate through all participants and add them to a collection of participants to update with our new setting. We skip over the local participant, because in the most common use case for this type of muting functionality the point is to mute all students, and not the instructor.

Finally, we make a single call to our Daily call object to update all participants using the updateParticipants() instance method and pass it our collection of participants to update with their new settings.

Our muting moderation feature is now complete!

Kronk saying "Mission Accomplished"

Now, let’s look at another common e-learning feature and how to implement it with Daily: transcription.


Transcription allows users to see a real time or after-the-fact recording of call audio. Using Daily’s WebRTC APIs, transcription can be enabled by using the Daily call object’s startTranscription() and stopTranscription() instance methods.

To utilize transcription features, your Daily domain must have transcription enabled via the enable_transcription property. You can find out more details about how to do this in our reference docs.

In our e-learning demo, moderators can enable transcription by clicking the “Show transcriptions” icon in the top right hand corner of the call window. This button is only enabled if transcription is enabled on the utilized Daily domain as described above.

To tell whether the transcription feature is enabled for the domain, we’ve used our useRoom() React hook in conjunction with an isTranscriptionEnabled useMemo hook.

const room = useRoom();
const isTranscriptionEnabled = useMemo(
    () => !!room?.domainConfig?.enable_transcription,

The useRoom() hook is provided by our own Daily React Hooks library and returns an object with room and domain properties.

If transcription is not supported for the domain, the moderator sees a message with a link on how to enable it if they wish:

Label saying "Transcription is not supported on your domain", with a link to the reference docs

Otherwise, we show the relevant button to toggle transcription to the moderator:

Label saying "Show Transcriptions" with a button to toggle transcription on the right

toggleTranscription() turns transcription on or off using the aforementioned Daily API methods.

const toggleTranscription = useCallback(async () => {
    if (isTranscribing) {
       await callObject.stopTranscription();
    else {
       await callObject.startTranscription();
  }, [callObject, isTranscribing]);

Video call with the teacher's transcription appearing above call controls
Transcription text being shown


We’ve implemented a whiteboard for our virtual class with the help of a JavaScript library called Zwibbler. The flexibility of our custom call object allowed us to seamlessly integrate this external whiteboard with the video call happening simultaneously. Zwibbler is just one example, however; the beauty of working with Daily is that you can integrate any third party whiteboard to meet your desired functionality. You can check out how we’re creating the Zwibbler board here.

A whiteboard with "hello" written on it on the left, and virtual class participant tiles on the right
Whiteboard integration

Raise Your Hand

Allowing students to raise their hand to get the instructor’s attention is a popular feature of e-learning applications. In our virtual class demo, students can raise their hands through the call controls at the bottom of the screen:

Participant clicking an icon to raise their hand in a video call
Raising your hand

Participants who have their hand raised have a little hand emoji (✋) appear next to their name in the application.

The Raise Your Hand feature is implemented using Daily’s sendAppMessage() call object instance method, which allows users to send data to one or all other call participants.

The Raise Your Hand control shown above is only visible to students and is shown via a conditional call control button in the BasicTray.js file:

      {!localParticipant?.owner && (
          label={isHandRaised ? 'Cancel': 'Hand'}
          <IconHand />

When clicked, the raiseHand() function is invoked to toggle the hand state:

  const raiseHand = useCallback(() => {
    setIsHandRaised(raise => !raise);
  }, [isHandRaised]);

The onHandRaise() function in turn does all the heavy lifting:

  const onHandRaise = useCallback((raiseHand) => {
    if (!localParticipant?.session_id) return;

    const sessionId = localParticipant.session_id;

    if (raiseHand) {
      setSharedState(sharedState => {
        return {
            [ Set(sharedState.handRaisedParticipants).add(sessionId)],
    } else {
      setSharedState(sharedState => {
        return {
            [...sharedState.handRaisedParticipants].filter(p => p !== sessionId),
  }, [localParticipant]);

Above, we retrieve the local participant’s session ID. Then, if the participant is raising their hand, we add their session ID to the collection of users with their hand raised in our shared state. If the participant is lowering their hand, we recreate the collection of participants raising their hand with the given session ID removed. setSharedState() then uses the aforementioned sendAppMessage() instance method to broadcast it to all other participants.


Another useful feature in an e-learning application is being able to run polls for students to respond to. Our virtual class demo shows one of the ways to implement polling functionality with Daily.

User clicing "Poll" button to create a poll
Creating a poll

In our demo, we implemented polls with the help of Daily's useAppMessage() React Hook. This hook provides a convenient way for us to send"app-message" events to call participants as well as handle incoming messages.

Let’s take a look at how we handle sending and receiving poll data:

  const sendAppMessage = useAppMessage({
    onAppMessage: useCallback(
      ev => {
        const msg = ev?.data?.message;
        const msgType = msg?.type;
        if (!msgType) return;

        switch (msgType) {
          case 'poll':
            openModal(localParticipant.owner ? POLL_RESULT_MODAL : POLL_MODAL);
          case 'selected-answer-poll':
            const newResults = results;
            newResults[msg.answer] = [...newResults[msg.answer],];
            setResults({ ...newResults });
          case 'conclude-poll':
      [results, localParticipant?.owner],

Above, we call useAppMessage(), which returns a Function you can call to send an app message to other call participants. We pass a callback parameter to this hook, which defines how we handle incoming app messages.

In the handling callback, we check the message type property. The message type can be one of the following:

  • ”poll”, indicating poll data being sent by the teacher
  • ”selected-answer-poll”, indicating an answer being sent by a student
  • ”conclude-poll”, indicating that the teacher has closed the poll

The data within the message can then be used to show the student the incoming poll, or show the teacher the student’s answer. In this way, Daily's "app-message" events allow us to implement features like polling entirely on the client-side.

"Cats or dogs?" poll modal for a student to select their answer.
Poll for a student to answer
Poll results modal showing poll results
Poll results seen by the teacher

Customizing our e-learning demo

Feel free to clone our repo and customize our implementation for your own needs. You can check out our documentation on working with Daily’s custom call object in our getting started guide.

To live stream or not to live stream?

You may be wondering if you'll need to use live streaming as opposed to a real-time, fully interactive call for larger audiences. Daily currently supports up to 1000 people in a real-time call, which allows you to take advantage of full teacher-student interactivity for your e-learning application. For larger participant sizes, check out our guide on live streaming with Daily. If you're not sure which approach is right for you, please don't hesitate to reach out! We'd love to help you with your specific use case.

Other e-learning applications

Our existing fitness app and ongoing series also showcase how developers can build e-learning features with Daily. There’s a lot of feature overlap between these two use cases. Our fitness demo showcases implementation for creating private or public classes, displaying existing classes to students, avoiding distractions by letting instructors mute students, and more! Check out that series to get up to speed with building these features with Daily.

Please feel free to contact us if you have any questions about building e-learning applications with Daily!