<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>  Blog</title>
        <link>https://www.plugnmeet.org/blog</link>
        <description>  Blog</description>
        <lastBuildDate>Mon, 27 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 MynaParrot SL.</copyright>
        <item>
            <title><![CDATA[Build Your Own Mobile Video App with plugNmeet's Hybrid Integration]]></title>
            <link>https://www.plugnmeet.org/blog/2026/07/27/build-custom-video-conference-app</link>
            <guid>https://www.plugnmeet.org/blog/2026/07/27/build-custom-video-conference-app</guid>
            <pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn to build a self-hosted, white-label video conference app for iOS, Android, or desktop using plugNmeet's hybrid integration. Get the power of native media and the simplicity of our web UI.]]></description>
            <content:encoded><![CDATA[<p>Building a custom video conferencing app for mobile or desktop is a massive undertaking. You need to handle device permissions, media capture, network instability, and build a complex user interface from scratch. What if you could get the performance and power of a native app without having to reinvent the entire video conferencing wheel?</p>
<p>Today, we're giving you a sneak peek at the solution. Introducing <strong>plugNmeet's Hybrid Integration</strong>, a powerful new way to build a <strong>custom, self-hosted video conferencing app</strong> for iOS, Android, Flutter, React Native, or desktop. It combines the raw power of native media handling with the complete, feature-rich plugNmeet web client, giving you an accelerated path to launching your own <strong>white-label video app</strong>.</p>
<div class="theme-admonition theme-admonition-info admonition_uXJb alert alert--info"><div class="admonitionHeading_OYp7"><span class="admonitionIcon_OJue"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Upcoming Feature</div><div class="admonitionContent__NE8"><p>This hybrid integration is an upcoming feature planned for <strong>v2.4.0</strong>. A demo server and open-source example applications are available now for testing and development.</p></div></div>
<p><strong>A quick note on our philosophy</strong>: plugNmeet's core focus has always been on delivering a powerful, browser-first video conferencing experience. We are not shifting to become an app-first company. Instead, this hybrid integration is our answer to the many developers in our community who need to build native mobile or desktop applications.</p>
<p>You might be wondering why we didn't create our own dedicated mobile SDK. The answer is simple: we don't need to! plugNmeet is built on LiveKit, which already provides robust native SDKs for every major platform, including <a href="https://github.com/livekit/client-sdk-android" target="_blank" rel="noopener noreferrer" class="">Android</a>, <a href="https://github.com/livekit/client-sdk-react-native" target="_blank" rel="noopener noreferrer" class="">React Native</a>, and <a href="https://github.com/livekit/client-sdk-flutter" target="_blank" rel="noopener noreferrer" class="">Flutter</a>. Our hybrid approach leverages these battle-tested LiveKit SDKs for native media publishing, combined with our feature-rich web client in a WebView. This means you get the best of both worlds: ready-to-use native media capabilities and plugNmeet's comprehensive UI, all without us having to duplicate effort. We are providing a robust, flexible pathway for <em>you</em> to build <em>your</em> own app, your way, without compromising on the self-hosted, open-source principles that define plugNmeet.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-best-of-both-worlds-native-power-web-simplicity">The Best of Both Worlds: Native Power, Web Simplicity<a href="https://www.plugnmeet.org/blog/2026/07/27/build-custom-video-conference-app#the-best-of-both-worlds-native-power-web-simplicity" class="hash-link" aria-label="Direct link to The Best of Both Worlds: Native Power, Web Simplicity" title="Direct link to The Best of Both Worlds: Native Power, Web Simplicity" translate="no">​</a></h3>
<p>The hybrid model is a game-changer for developers who need more than just a generic, third-party meeting link. It gives you the flexibility of native development and the speed of using the existing plugNmeet meeting UI.</p>
<p>Here’s why this is such a big deal:</p>
<ul>
<li class=""><strong>Native Media Performance:</strong> Capture and publish media using platform-native APIs. This is a must-have for features like <strong>mobile screen sharing</strong>, which is not supported by most mobile browsers. It also opens the door for advanced features like native virtual backgrounds or custom audio processing.</li>
<li class=""><strong>Faster App Development:</strong> Your team can focus on building the native shell and core application experience. You don't have to spend months recreating participant lists, chat, a whiteboard, moderation controls, and complex video layouts. We’ve already built that for you.</li>
<li class=""><strong>True White-Label Customization:</strong> You control the entire user experience, from the app icon and login screen to the in-meeting branding. The meeting feels like a core part of your application, not a bolted-on third-party service.</li>
<li class=""><strong>Self-Hosted and Secure:</strong> As always with plugNmeet, you own your infrastructure and your data. All communications are routed through your on-premise server, ensuring maximum privacy, compliance, and control.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="how-it-works-an-elegant-architecture">How It Works: An Elegant Architecture<a href="https://www.plugnmeet.org/blog/2026/07/27/build-custom-video-conference-app#how-it-works-an-elegant-architecture" class="hash-link" aria-label="Direct link to How It Works: An Elegant Architecture" title="Direct link to How It Works: An Elegant Architecture" translate="no">​</a></h3>
<p>The core idea is simple yet powerful: one logical user is represented by two participants connected to your self-hosted LiveKit instance.</p>
<ul>
<li class="">The <strong>web participant</strong> runs in a WebView inside your app. It handles the entire user interface and subscribes to all media streams from other users.</li>
<li class="">The <strong>native participant</strong> runs in your native app code. Its only job is to capture and publish the user's own camera, microphone, and screen share using native APIs.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Hybrid Integration Architecture" src="https://www.plugnmeet.org/assets/images/hybrid-architecture-7f12c387c781c5a76acbd94bcaf74031.png" width="3584" height="1884" class="img_jmCp"></p>
<p>A lightweight communication "bridge" allows the web UI and the native code to communicate seamlessly. When a user taps the "Mute" button in the web interface, a message is sent across the bridge, telling your native code to mute the microphone. This architecture provides a seamless user experience while giving you the performance benefits of native code.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="get-a-head-start-on-your-custom-video-app">Get a Head Start on Your Custom Video App<a href="https://www.plugnmeet.org/blog/2026/07/27/build-custom-video-conference-app#get-a-head-start-on-your-custom-video-app" class="hash-link" aria-label="Direct link to Get a Head Start on Your Custom Video App" title="Direct link to Get a Head Start on Your Custom Video App" translate="no">​</a></h3>
<p>While the official release is just around the corner, you can start building today. We believe in building in the open, and we're excited to see what our community creates.</p>
<hr>
<p><strong>Ready to build your own video conferencing app?</strong></p>
<ul>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/developer-guide/mobile-app-integration">Explore the Technical Guide</a></strong> for a deep dive into the architecture and API.</li>
<li class=""><strong><a href="https://github.com/mynaparrot/plugnmeet-mobile-app" target="_blank" rel="noopener noreferrer" class="">Check out the Demo Apps on GitHub</a></strong> to see the pattern in action.</li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a></strong> to explore plugNmeet's features.</li>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/installation">Follow our Installation Guide</a></strong> to get your self-hosted server running.</li>
</ul>]]></content:encoded>
            <category>video-conferencing</category>
            <category>mobile-app</category>
            <category>sdk</category>
            <category>self-hosted</category>
            <category>open-source</category>
            <category>webrtc</category>
            <category>white-label</category>
            <category>ios</category>
            <category>android</category>
            <category>flutter</category>
            <category>react-native</category>
        </item>
        <item>
            <title><![CDATA[plugNmeet OpenAI Integration: Self-Hosted AI for Video Conferencing]]></title>
            <link>https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet</link>
            <guid>https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet</guid>
            <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Configure OpenAI-compatible APIs in plugNmeet to enable live transcription, real-time translation, spoken translations, AI chat, and post-meeting summaries on your self-hosted video conferencing server.]]></description>
            <content:encoded><![CDATA[<p>At <strong>plugNmeet</strong>, our open-source WebRTC video conferencing platform is built around flexibility, privacy, and control. While plugNmeet already supports specialized cognitive services from <strong>Azure</strong> and <strong>Google</strong>, we are excited to introduce a major upgrade to the <strong>Insights</strong> framework: the official <strong>OpenAI Provider for plugNmeet</strong>.</p>
<p>With this integration, you can bring powerful AI features to your self-hosted video conferencing server, including <strong>live transcription</strong>, <strong>real-time translation</strong>, <strong>live chat translation</strong>, <strong>spoken translations using text-to-speech</strong>, <strong>AI meeting summaries</strong>, and an <strong>in-meeting AI chatbot</strong>.</p>
<p>Even better, the provider is designed around the standard OpenAI API format, which means you can use OpenAI directly or connect plugNmeet to any <strong>OpenAI-compatible API</strong>, including alternative AI providers and self-hosted LLM platforms.</p>
<div class="theme-admonition theme-admonition-info admonition_uXJb alert alert--info"><div class="admonitionHeading_OYp7"><span class="admonitionIcon_OJue"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Sneak Peek &amp; Early Access</div><div class="admonitionContent__NE8"><p>This feature is currently in the final stages of review and is planned for an upcoming release.</p><p>Developers who want to test it early or review the implementation can follow the GitHub Pull Request:</p><p><a href="https://github.com/mynaparrot/plugNmeet-server/pull/883" target="_blank" rel="noopener noreferrer" class="">https://github.com/mynaparrot/plugNmeet-server/pull/883</a></p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="why-this-matters-for-self-hosted-video-conferencing">Why This Matters for Self-Hosted Video Conferencing<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#why-this-matters-for-self-hosted-video-conferencing" class="hash-link" aria-label="Direct link to Why This Matters for Self-Hosted Video Conferencing" title="Direct link to Why This Matters for Self-Hosted Video Conferencing" translate="no">​</a></h2>
<p>AI features are becoming essential for modern online meetings. Teams increasingly expect searchable transcripts, multilingual communication, automated summaries, and intelligent meeting assistants.</p>
<p>However, many hosted video conferencing platforms lock these features behind proprietary services. plugNmeet takes a different approach.</p>
<p>With the OpenAI provider, you can choose the AI backend that best fits your needs:</p>
<ul>
<li class="">Use <strong>OpenAI</strong> for high-quality managed AI services.</li>
<li class="">Use <strong>Azure OpenAI</strong> or another cloud provider with an OpenAI-compatible interface.</li>
<li class="">Use <strong>Groq</strong>, <strong>Together AI</strong>, <strong>Anyscale</strong>, or another compatible API provider.</li>
<li class="">Use <strong>Ollama</strong>, <strong>LocalAI</strong>, or another self-hosted LLM platform for more control over privacy and infrastructure.</li>
<li class="">Optimize your setup for cost, performance, latency, compliance, or data ownership.</li>
</ul>
<p>This makes plugNmeet a strong choice for organizations that want <strong>AI-powered video conferencing without giving up control of their infrastructure</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="key-features-of-the-plugnmeet-openai-provider">Key Features of the plugNmeet OpenAI Provider<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#key-features-of-the-plugnmeet-openai-provider" class="hash-link" aria-label="Direct link to Key Features of the plugNmeet OpenAI Provider" title="Direct link to Key Features of the plugNmeet OpenAI Provider" translate="no">​</a></h2>
<p>By enabling the OpenAI provider in plugNmeet, you can unlock a full set of AI-powered meeting features.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="live-transcription">Live Transcription<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#live-transcription" class="hash-link" aria-label="Direct link to Live Transcription" title="Direct link to Live Transcription" translate="no">​</a></h3>
<p>The OpenAI provider supports real-time speech-to-text through a <strong>WebSocket-based connection</strong>. This enables low-latency transcription during live meetings.</p>
<p>Live transcription is useful for:</p>
<ul>
<li class="">Accessibility</li>
<li class="">Meeting notes</li>
<li class="">Searchable conversation history</li>
<li class="">Real-time translation workflows</li>
<li class="">Post-meeting summaries</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="real-time-multilingual-translation">Real-Time Multilingual Translation<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#real-time-multilingual-translation" class="hash-link" aria-label="Direct link to Real-Time Multilingual Translation" title="Direct link to Real-Time Multilingual Translation" translate="no">​</a></h3>
<p>plugNmeet can translate finalized transcription text during the meeting. This helps participants communicate across languages and reduces language barriers in international meetings, online classes, webinars, and remote team discussions.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="spoken-translations-with-text-to-speech">Spoken Translations with Text-to-Speech<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#spoken-translations-with-text-to-speech" class="hash-link" aria-label="Direct link to Spoken Translations with Text-to-Speech" title="Direct link to Spoken Translations with Text-to-Speech" translate="no">​</a></h3>
<p>The integration also supports spoken translations using text-to-speech. A translator bot can join the meeting and speak translated content aloud using an AI-generated voice.</p>
<p>This is especially useful for:</p>
<ul>
<li class="">Multilingual events</li>
<li class="">Remote classrooms</li>
<li class="">Global team meetings</li>
<li class="">Cross-border business calls</li>
<li class="">Accessibility-focused deployments</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="live-chat-translation">Live Chat Translation<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#live-chat-translation" class="hash-link" aria-label="Direct link to Live Chat Translation" title="Direct link to Live Chat Translation" translate="no">​</a></h3>
<p>The OpenAI provider can also translate chat messages in real time. This allows participants from different linguistic backgrounds to communicate seamlessly through text chat, making meetings more inclusive and productive. Each user can select their preferred language and view translated messages instantly.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="automated-meeting-summarization">Automated Meeting Summarization<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#automated-meeting-summarization" class="hash-link" aria-label="Direct link to Automated Meeting Summarization" title="Direct link to Automated Meeting Summarization" translate="no">​</a></h3>
<p>After a meeting ends, plugNmeet can generate a concise AI-powered summary from the meeting transcript.</p>
<p>Summaries can include:</p>
<ul>
<li class="">Key discussion points</li>
<li class="">Important decisions</li>
<li class="">Action items</li>
<li class="">Follow-up tasks</li>
<li class="">Meeting outcomes</li>
</ul>
<p>This helps users save time and makes recorded meetings easier to review.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="in-meeting-ai-chatbot">In-Meeting AI Chatbot<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#in-meeting-ai-chatbot" class="hash-link" aria-label="Direct link to In-Meeting AI Chatbot" title="Direct link to In-Meeting AI Chatbot" translate="no">​</a></h3>
<p>The OpenAI provider can also power an AI chatbot inside plugNmeet conference rooms. Participants can interact with the assistant during the meeting for contextual help, questions, and AI-powered support.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="how-to-configure-openai-in-plugnmeet">How to Configure OpenAI in plugNmeet<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#how-to-configure-openai-in-plugnmeet" class="hash-link" aria-label="Direct link to How to Configure OpenAI in plugNmeet" title="Direct link to How to Configure OpenAI in plugNmeet" translate="no">​</a></h3>
<p>Configuring the OpenAI integration on your self-hosted server is straightforward. All you need is an active API key and a quick update to your <code>config.yaml</code>.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="1-define-your-provider-account">1. Define Your Provider Account<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#1-define-your-provider-account" class="hash-link" aria-label="Direct link to 1. Define Your Provider Account" title="Direct link to 1. Define Your Provider Account" translate="no">​</a></h4>
<p>Add your provider account under the <code>insights.providers</code> section:</p>
<div class="language-yaml codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-yaml codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">insights</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">enabled</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">providers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">openai</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># A unique name for this account</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">credentials</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">api_key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"YOUR_API_KEY"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">options</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># This is the magic key for compatibility! For local LLMs (Ollama), use e.g. "http://localhost:11434/v1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">endpoint</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://api.openai.com/v1"</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="beyond-openai-universal-api-compatibility">Beyond OpenAI: Universal API Compatibility<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#beyond-openai-universal-api-compatibility" class="hash-link" aria-label="Direct link to Beyond OpenAI: Universal API Compatibility" title="Direct link to Beyond OpenAI: Universal API Compatibility" translate="no">​</a></h4>
<p>The <code>endpoint</code> option is your gateway to a universe of AI providers. Because our integration is built on the standard OpenAI API specification, you can use <strong>any service that offers an OpenAI-compatible API</strong>.</p>
<p>This means you are not locked into a single vendor. You can point plugNmeet to:</p>
<ul>
<li class=""><strong>Alternative AI Providers</strong>: Services like <strong>Groq</strong>, <strong>Together AI</strong>, or <strong>Anyscale</strong>.</li>
<li class=""><strong>Self-Hosted Open Source Models</strong>: Run your own LLMs locally using tools like <strong>Ollama</strong> or <strong>LocalAI</strong>.</li>
<li class=""><strong>Major Cloud Providers</strong>: Use services like <strong>Azure OpenAI</strong> which provide a compatible API layer.</li>
</ul>
<p>This flexibility allows you to optimize for cost, privacy, and performance without changing your plugNmeet setup.</p>
<div class="theme-admonition theme-admonition-info admonition_uXJb alert alert--info"><div class="admonitionHeading_OYp7"><span class="admonitionIcon_OJue"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>A Note on Endpoints and WebSockets</div><div class="admonitionContent__NE8"><p>A key requirement for the <strong>Live Transcription</strong> service is that your endpoint must support <strong>WebSocket connections</strong> (<code>ws://</code> or <code>wss://</code>).</p><p>Our provider will automatically attempt to upgrade the HTTP endpoint you provide to its WebSocket equivalent (e.g., <code>https://api.example.com</code> becomes <code>wss://api.example.com</code>), but your server must be configured to handle this connection type. Other services like TTS and summarization will continue to use standard HTTP requests.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="important-enabling-live-transcription-translation">Important: Enabling Live Transcription Translation<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#important-enabling-live-transcription-translation" class="hash-link" aria-label="Direct link to Important: Enabling Live Transcription Translation" title="Direct link to Important: Enabling Live Transcription Translation" translate="no">​</a></h3>
<p>One of the most powerful features is the ability to translate live transcriptions in real time. To enable this, there is a crucial dependency to be aware of:</p>
<p><strong>You must have both the <code>transcription</code> and <code>translation</code> services configured and enabled.</strong></p>
<p>The translation process works by taking the finalized text from the transcription service and passing it to the language model configured in the <code>translation</code> service. Without the <code>translation</code> configuration, the system won't know which model to use for the translation task, and it will fail silently.</p>
<p>Therefore, a minimal setup for live transcription with translation would look like this:</p>
<div class="language-yaml codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-yaml codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">transcription</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">translation</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">options</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># This model will be used for real-time transcription and chat translation.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-5.4-mini"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">max_selected_trans_langs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="complete-service-configuration-examples">Complete Service Configuration Examples<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#complete-service-configuration-examples" class="hash-link" aria-label="Direct link to Complete Service Configuration Examples" title="Direct link to Complete Service Configuration Examples" translate="no">​</a></h3>
<p>Here is a comprehensive guide to all the services and their available options. You can uncomment and adjust them to fine-tune the behavior.</p>
<div class="language-yaml codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-yaml codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Service 1: Live Transcription (Real-time Speech-to-Text)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># This service uses a real-time WebSocket API for live audio streams.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">transcription</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">options</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># The model for real-time transcription.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">realtime_transcription_model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-realtime-whisper"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># --- Advanced Customization (Optional) ---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Use a custom authentication header if your provider (like Azure)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># expects something other than the default 'Authorization' header.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># realtime_auth_header: "api-key"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Latency vs. accuracy tradeoff for gpt-realtime-whisper.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Supported values: "minimal", "low", "medium", "high", "xhigh".</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># transcription_delay: "medium"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Voice Activity Detection (VAD) mode. For gpt-realtime-whisper,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># this should effectively be 'manual', where plugNmeet commits audio.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Other modes like 'server_vad' may be supported by other models.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># transcription_turn_detection: "manual"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Fine-tune the 'manual' VAD behavior.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># transcription_min_commit_ms: 1500       # Min duration of speech to be a valid segment.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># transcription_silence_commit_ms: 900    # Silence duration that triggers a final transcript.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># transcription_max_commit_ms: 12000      # Max duration of a segment before forcing a final transcript.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># transcription_speech_rms: 500           # Audio level (RMS) threshold to detect speech.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># transcription_max_buffered_silence_ms: 400 # Pre-speech silence to include for better context.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Service 2: Real-time and Chat Translation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">translation</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">options</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># The model to use for translating text (from chat or transcriptions).</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-5.4-mini"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># --- Advanced Customization (Optional) ---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Maximum number of languages a user can select for translation.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># max_selected_trans_langs: 5</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Service 3: Text-to-Speech (TTS) for Spoken Translations</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">speech-synthesis</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">options</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># The TTS model to use (e.g., "tts-1", "tts-1-hd").</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-4o-mini-tts"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># --- Advanced Customization (Optional) ---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># OpenAI's TTS models require a 24000Hz sample rate. This is set</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># automatically but can be overridden if your provider requires a different rate.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># tts_sample_rate: 24000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># The default voice to use if a language-specific one isn't set.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Voices: alloy, echo, fable, onyx, nova, shimmer.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># default_voice: "alloy"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Assign specific voices to different languages.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># voice-en: "alloy"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># voice-es: "nova"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># voice-fr: "shimmer"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Service 4: AI-Powered Chat</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">ai_text_chat</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">options</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># This model is used for the in-meeting AI assistant.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">chat_model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-5.4"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># --- Advanced Customization (Optional) ---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># The model used to summarize chat history when the context window is full.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">summarize_model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-5.4-mini"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Number of recent messages to keep in memory before summarizing.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># context_window: 5</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Service 5: Post-Meeting Summarization (Batch Audio File)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># This service transcribes a recorded audio file and then summarizes it.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">meeting_summarizing</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">id</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default-openai"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">options</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># The model for the initial audio-to-text transcription.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">transcription_model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-4o-transcribe"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># --- Advanced Customization (Optional) ---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">#</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># The model for the final summarization of the transcript.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">summarize_model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-5.4-mini"</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="a-more-intelligent-and-open-future">A More Intelligent and Open Future<a href="https://www.plugnmeet.org/blog/2026/07/13/self-hosted-video-conferencing-ai-openai-plugnmeet#a-more-intelligent-and-open-future" class="hash-link" aria-label="Direct link to A More Intelligent and Open Future" title="Direct link to A More Intelligent and Open Future" translate="no">​</a></h3>
<p>The new OpenAI provider is a powerful addition to the plugNmeet Insights framework, offering more choice, flexibility, and intelligence than ever before. By embracing the OpenAI API standard, we're giving you the freedom to choose the best tools for the job—whether that's OpenAI's own models, a major cloud service like Azure, or a self-hosted solution.</p>
<hr>
<p><strong>Ready to build your own video conferencing platform?</strong></p>
<ul>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/installation">Follow our Installation Guide</a> to get your self-hosted server running in minutes.</strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a> to explore the features.</strong></li>
</ul>]]></content:encoded>
            <category>plugnmeet</category>
            <category>open-source</category>
            <category>WebRTC</category>
            <category>openai</category>
            <category>transcription</category>
            <category>translation</category>
            <category>summarization</category>
            <category>tts</category>
            <category>compatible-api</category>
            <category>self-hosted-llm</category>
            <category>websocket</category>
            <category>video-conferencing</category>
            <category>ai-meetings</category>
            <category>ai</category>
        </item>
        <item>
            <title><![CDATA[Mastering Your Media Workflows: Introducing Scripting and Storage Hooks]]></title>
            <link>https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks</link>
            <guid>https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks</guid>
            <pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[At plugNmeet, our goal is to provide a powerful, open, and flexible platform that adapts to your needs, not the other way around. As deployments grow and move to the cloud, managing media files like recordings and artifacts becomes a critical challenge. How do you store files in S3? How do you automate notifications when a recording is ready? How do you manage complex workflows in a multi-server cluster?]]></description>
            <content:encoded><![CDATA[<p>At plugNmeet, our goal is to provide a powerful, open, and flexible platform that adapts to your needs, not the other way around. As deployments grow and move to the cloud, managing media files like recordings and artifacts becomes a critical challenge. How do you store files in S3? How do you automate notifications when a recording is ready? How do you manage complex workflows in a multi-server cluster?</p>
<p>Until now, solving these challenges often required modifying the core application code, making updates difficult and maintenance a chore.</p>
<p>Today, we're thrilled to announce a powerful new feature that changes everything: <strong>Scripting and Storage Hooks</strong>. This new mechanism allows you to extend and customize plugNmeet's media workflow by executing your own custom scripts—either as efficient, <strong>long-lived processes</strong> or simple <strong>one-shot commands</strong>—at key stages. All communication happens via <strong>newline-delimited JSON</strong> over standard I/O, so you can integrate your own logic without touching a single line of the core code.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="why-hooks-the-power-of-a-decoupled-architecture">Why Hooks? The Power of a Decoupled Architecture<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#why-hooks-the-power-of-a-decoupled-architecture" class="hash-link" aria-label="Direct link to Why Hooks? The Power of a Decoupled Architecture" title="Direct link to Why Hooks? The Power of a Decoupled Architecture" translate="no">​</a></h3>
<p>So, why is this a game-changer? Hooks allow you to <strong>decouple</strong> your business logic from the application's logic. Instead of being locked into local disk storage, you can now integrate with virtually any external service.</p>
<p>Here’s what you can do with hooks:</p>
<ul>
<li class=""><strong>Integrate Any Storage Provider:</strong> Use AWS S3, Google Cloud Storage, Backblaze, MinIO, or even a custom FTP server. If you can write a script for it, you can use it.</li>
<li class=""><strong>Automate Custom Workflows:</strong> Automatically send a Slack notification when a recording is processed, call a CRM API with meeting analytics, or trigger a video indexing service.</li>
<li class=""><strong>Enable Complex, Scalable Deployments:</strong> In a multi-server cluster, hooks are essential. A recorder node can automatically upload a raw recording to a central S3 bucket, and a separate transcoder node can be triggered to download it, process it, and re-upload the final MP4.</li>
<li class=""><strong>Future-Proof Your Setup:</strong> Your storage strategy can evolve without being tied to the plugNmeet release cycle.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="how-it-works-core-concepts">How It Works: Core Concepts<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#how-it-works-core-concepts" class="hash-link" aria-label="Direct link to How It Works: Core Concepts" title="Direct link to How It Works: Core Concepts" translate="no">​</a></h3>
<p>The new system is divided into two distinct but related mechanisms: <strong>Recorder Hooks</strong> and <strong>Server Storage Hooks</strong>. Before diving into the specifics, let's cover the core concepts that apply to all hooks.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="execution-models-long-lived-vs-one-shot">Execution Models: Long-Lived vs. One-Shot<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#execution-models-long-lived-vs-one-shot" class="hash-link" aria-label="Direct link to Execution Models: Long-Lived vs. One-Shot" title="Direct link to Execution Models: Long-Lived vs. One-Shot" translate="no">​</a></h4>
<p>For maximum flexibility, you can run your hooks in two ways:</p>
<ul>
<li class=""><strong>Long-Lived Processes (Recommended)</strong>: Your script is launched once and runs continuously, listening for requests on <code>stdin</code>. This is highly efficient as it avoids process startup overhead for every event.</li>
<li class=""><strong>One-shot Commands</strong>: For simpler tasks, you can execute a command for each event. This is great for <code>curl</code>, <code>wget</code>, or the built-in utility.</li>
</ul>
<p>Speaking of which, we've included a handy one-shot command to simplify calling external APIs:</p>
<ul>
<li class=""><strong>Built-in <code>http-request</code> Utility</strong>: A simple command for sending the hook's JSON payload to an HTTP/HTTPS endpoint.<!-- -->
<ul>
<li class=""><strong>Usage:</strong> <code>http-request &lt;URL&gt;</code></li>
</ul>
</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="the-pipeline-model">The Pipeline Model<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#the-pipeline-model" class="hash-link" aria-label="Direct link to The Pipeline Model" title="Direct link to The Pipeline Model" translate="no">​</a></h4>
<p>If you define multiple scripts for a single hook, they form a pipeline. The <code>stdout</code> response from the first script becomes the <code>stdin</code> request for the second, and so on. If a script in the chain doesn't need to modify the data, it <strong>must</strong> still pass the original, unmodified JSON object through to <code>stdout</code>.</p>
<div class="theme-admonition theme-admonition-danger admonition_uXJb alert alert--danger"><div class="admonitionHeading_OYp7"><span class="admonitionIcon_OJue"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>File Cleanup is Your Responsibility</div><div class="admonitionContent__NE8"><p>When the hook system is enabled, plugNmeet delegates file management to your scripts. If a hook provides you with a temporary local file (e.g., via <code>input_path</code>), <strong>plugNmeet will not delete that file</strong>.</p><p>Your script is responsible for cleaning up the local source file after it has been processed (e.g., after uploading it to remote storage). This is critical to prevent your server's disk from filling up.</p></div></div>
<div class="theme-admonition theme-admonition-warning admonition_uXJb alert alert--warning"><div class="admonitionHeading_OYp7"><span class="admonitionIcon_OJue"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Path Consistency is Your Responsibility</div><div class="admonitionContent__NE8"><p>plugNmeet <strong>does not validate</strong> the <code>output_path</code> you return from a hook. It is stored as a string and used as the <code>input_path</code> for subsequent <code>download_hook</code> and <code>delete_hook</code> calls.</p><ul>
<li class="">
<p><strong>If your script modifies <code>output_path</code></strong> (e.g., changing a local path to an S3 key in an <code>upload_hook</code> or <code>post_transcoding</code> hook), you take full responsibility for that path. You <strong>MUST</strong> also implement a corresponding <code>download_hook</code> and <code>delete_hook</code> that can understand and process the custom path format you have defined.</p>
</li>
<li class="">
<p><strong>If your script is only for observation</strong> (e.g., logging stats or sending a notification) and does not modify the <code>output_path</code>, then you do not need to provide the other hooks. The default workflow will continue with the original path.</p>
</li>
</ul><p>Failing to provide compatible <code>download_hook</code> and <code>delete_hook</code> scripts after changing the <code>output_path</code> will result in broken downloads and deletions.</p></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="communication-protocol">Communication Protocol<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#communication-protocol" class="hash-link" aria-label="Direct link to Communication Protocol" title="Direct link to Communication Protocol" translate="no">​</a></h4>
<ul>
<li class=""><strong><code>stdin</code></strong>: Your script must read from <code>stdin</code> in a loop. Each line it reads will be a complete JSON object representing a single request from the plugNmeet component.</li>
<li class=""><strong><code>stdout</code></strong>: For each request it receives, your script <strong>MUST</strong> print a single line of JSON to <code>stdout</code>. This line is the response.</li>
<li class=""><strong><code>stderr</code></strong>: You can use <code>stderr</code> for logging within your script. This output is ignored by plugNmeet but is invaluable for debugging your custom logic.</li>
</ul>
<div class="theme-admonition theme-admonition-danger admonition_uXJb alert alert--danger"><div class="admonitionHeading_OYp7"><span class="admonitionIcon_OJue"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>IMPORTANT: Scripts Must Always Respond</div><div class="admonitionContent__NE8"><p>The call to execute a hook script is <strong>blocking</strong>. Your script <strong>MUST</strong> write a response to <code>stdout</code> for every request it receives on <code>stdin</code>. If a script fails to return a response, the plugNmeet service will hang indefinitely, waiting for the script to finish.</p><p>If a script does not need to modify the data (e.g., a script that only calls an external API for logging), it <strong>must</strong> still return the original, unmodified JSON object it received.</p></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="error-handling">Error Handling<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#error-handling" class="hash-link" aria-label="Direct link to Error Handling" title="Direct link to Error Handling" translate="no">​</a></h4>
<p>If your script encounters an error, it should populate the <code>error</code> field in its JSON response to <code>stdout</code>. The main application will log this error. It is crucial to always return the complete input JSON object, with the <code>error</code> field populated, to ensure subsequent scripts in a pipeline receive the expected data structure.</p>
<p>If the response is not valid JSON, plugNmeet logs a <strong>warning</strong> and discards the output. The <em>original</em> JSON data (the <code>stdin</code> to your script) is passed to the next script in the pipeline. This prevents a single faulty script from breaking the entire chain.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="server-hooks-managing-artifacts-and-downloads">Server Hooks: Managing Artifacts and Downloads<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#server-hooks-managing-artifacts-and-downloads" class="hash-link" aria-label="Direct link to Server Hooks: Managing Artifacts and Downloads" title="Direct link to Server Hooks: Managing Artifacts and Downloads" translate="no">​</a></h3>
<p>These hooks are designed to manage how the main plugNmeet server handles files, primarily the various <strong>room artifacts</strong> and user-facing downloads.</p>
<ul>
<li class=""><strong><code>upload_hook</code></strong>: Triggered when the server creates an artifact (e.g., a meeting analytics file). Your script takes the local file (<code>input_path</code>) and uploads it to your provider, returning a unique <code>output_path</code> that tells the server how to find it later.</li>
<li class=""><strong><code>download_hook</code></strong>: Triggered when a user requests to download an artifact or a recording. Your script receives the file's identifier (<code>input_path</code>) and tells the server how to proceed (e.g., by providing a <code>redirect_url</code> or a <code>serve_local</code> path).</li>
<li class=""><strong><code>delete_hook</code></strong>: Triggered when a file needs to be removed. Your script receives the file's identifier (<code>input_path</code>) and handles the deletion from your external storage.</li>
<li class=""><strong><code>resumable_upload_hook</code></strong>: Handles chunked file uploads for the chat feature. Your script manages checking, uploading, and merging file parts, which is ideal for offloading to services like S3 Multipart Upload.</li>
<li class=""><strong><code>room_end_hook</code></strong>: Performs cleanup tasks after a room session ends, such as cleaning up any abandoned resumable upload chunks.</li>
</ul>
<p>The recommended approach for downloads is to return a temporary, pre-signed URL (<code>action: "redirect"</code>). This way, the user downloads the file directly from your storage provider (e.g., S3), and the traffic doesn't even hit your plugNmeet server, saving you bandwidth and resources.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="recorder-hooks-automating-the-recording-pipeline">Recorder Hooks: Automating the Recording Pipeline<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#recorder-hooks-automating-the-recording-pipeline" class="hash-link" aria-label="Direct link to Recorder Hooks: Automating the Recording Pipeline" title="Direct link to Recorder Hooks: Automating the Recording Pipeline" translate="no">​</a></h3>
<p>These hooks give you control over the recording and transcoding pipeline. While there are hooks for every stage (<code>post_recording</code>, <code>pre_transcoding</code>, <code>post_transcoding</code>), the most common use case is managing the final, processed file.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="example-1-full-s3-integration-for-server-hooks">Example 1: Full S3 Integration for Server Hooks<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#example-1-full-s3-integration-for-server-hooks" class="hash-link" aria-label="Direct link to Example 1: Full S3 Integration for Server Hooks" title="Direct link to Example 1: Full S3 Integration for Server Hooks" translate="no">​</a></h3>
<p>Let's walk through a real-world example of integrating S3 for all server-side file management using Node.js. With these three scripts, you have a complete, robust, and scalable storage solution for your plugNmeet server.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="project-setup">Project Setup<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#project-setup" class="hash-link" aria-label="Direct link to Project Setup" title="Direct link to Project Setup" translate="no">​</a></h4>
<div class="language-bash codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-bash codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token plain">mkdir plugnmeet-hooks</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd plugnmeet-hooks</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm init -y</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="server-configuration-serverconfigyaml">Server Configuration (<code>server/config.yaml</code>)<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#server-configuration-serverconfigyaml" class="hash-link" aria-label="Direct link to server-configuration-serverconfigyaml" title="Direct link to server-configuration-serverconfigyaml" translate="no">​</a></h4>
<div class="language-yaml codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-yaml codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">hooks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">upload_hook</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">pool_size</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Optional: controls parallel execution</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">scripts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">script</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/path/to/plugnmeet-hooks/upload.js"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">is_one_shot</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># This is a long-lived script</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">download_hook</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">scripts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">script</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/path/to/plugnmeet-hooks/download.js"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">is_one_shot</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">delete_hook</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">scripts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">script</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/path/to/plugnmeet-hooks/delete.js"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">is_one_shot</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">room_end_hook</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">scripts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">script</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"http-request http://localhost:8090/room-ended-notification"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">is_one_shot</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="the-upload-script-uploadjs">The Upload Script (<code>upload.js</code>)<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#the-upload-script-uploadjs" class="hash-link" aria-label="Direct link to the-upload-script-uploadjs" title="Direct link to the-upload-script-uploadjs" translate="no">​</a></h4>
<p>This script uploads server-generated artifacts (like analytics) to S3.</p>
<div class="language-javascript codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-javascript codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token hashbang comment" style="color:#999988;font-style:italic">#!/usr/bin/env node</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">S3Client</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">PutObjectCommand</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@aws-sdk/client-s3"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> createReadStream</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> unlinkSync </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fs"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> basename </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"path"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">readline</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"readline"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> s3Client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">S3Client</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">region</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"us-east-1"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-plugnmeet-artifacts"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// A bucket for artifacts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">log</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">S3Upload: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> rl </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> readline</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">createInterface</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">input</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdin</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">terminal</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"S3 artifact upload hook script started."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">rl</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"line"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">parse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> input_path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> room_id </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> output_path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">artifacts/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">room_id</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation function" style="color:#d73a49">basename</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation">input_path</span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> command </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">PutObjectCommand</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">Bucket</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">Key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> output_path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">Body</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createReadStream</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input_path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> s3Client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">command</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Successfully uploaded to </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">output_path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">unlinkSync</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input_path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Cleaned up local file: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">input_path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cleanupError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Error cleaning up file: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">cleanupError</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">output_path</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> output_path</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Error: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> errorResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">output_path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">JSON parse error: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">errorResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="the-download-script-downloadjs">The Download Script (<code>download.js</code>)<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#the-download-script-downloadjs" class="hash-link" aria-label="Direct link to the-download-script-downloadjs" title="Direct link to the-download-script-downloadjs" translate="no">​</a></h4>
<p>This script is triggered when a user needs to access a file. It receives the S3 key in <code>input_path</code> and generates a temporary, secure, pre-signed URL for the user to download the file directly from S3.</p>
<div class="language-javascript codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-javascript codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token hashbang comment" style="color:#999988;font-style:italic">#!/usr/bin/env node</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">S3Client</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">GetObjectCommand</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@aws-sdk/client-s3"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> getSignedUrl </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@aws-sdk/s3-request-presigner"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">readline</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"readline"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> s3Client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">S3Client</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">region</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"us-east-1"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-plugnmeet-artifacts"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">log</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">S3Download: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> rl </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> readline</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">createInterface</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">input</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdin</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">terminal</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"S3 download hook script started."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">rl</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"line"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">parse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> input_path </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Note: You might have different buckets for artifacts and recordings.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// A real-world script would have logic to determine the correct bucket.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> bucket </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> input_path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">startsWith</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"recordings/"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-plugnmeet-recordings"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> command </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">GetObjectCommand</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Bucket</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> bucket</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> input_path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> redirect_url </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getSignedUrl</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">s3Client</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> command</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">expiresIn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3600</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Generated pre-signed URL for </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">input_path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">action</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"redirect"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">redirect_url</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> redirect_url</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Error: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> errorResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">JSON parse error: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">errorResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="the-delete-script-deletejs">The Delete Script (<code>delete.js</code>)<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#the-delete-script-deletejs" class="hash-link" aria-label="Direct link to the-delete-script-deletejs" title="Direct link to the-delete-script-deletejs" translate="no">​</a></h4>
<p>This script handles the removal of a file from your S3 bucket when it's deleted from plugNmeet.</p>
<div class="language-javascript codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-javascript codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token hashbang comment" style="color:#999988;font-style:italic">#!/usr/bin/env node</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">S3Client</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">DeleteObjectCommand</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@aws-sdk/client-s3"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">readline</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"readline"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> s3Client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">S3Client</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">region</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"us-east-1"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-plugnmeet-artifacts"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">log</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">S3Delete: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> rl </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> readline</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">createInterface</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">input</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdin</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">terminal</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"S3 delete hook script started."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">rl</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"line"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">parse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> input_path </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> bucket </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> input_path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">startsWith</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"recordings/"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-plugnmeet-recordings"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> command </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">DeleteObjectCommand</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Bucket</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> bucket</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> input_path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> s3Client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">command</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Successfully deleted </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">input_path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">msg</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"File deleted successfully"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Error: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> errorResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">JSON parse error: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">errorResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="example-2-completing-the-workflow-with-a-recorder-hook">Example 2: Completing the Workflow with a Recorder Hook<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#example-2-completing-the-workflow-with-a-recorder-hook" class="hash-link" aria-label="Direct link to Example 2: Completing the Workflow with a Recorder Hook" title="Direct link to Example 2: Completing the Workflow with a Recorder Hook" translate="no">​</a></h3>
<p>Now, let's create the most important script for the recording workflow: the <code>post_transcoding</code> hook. In many setups, you only need to act after the final MP4 is ready. This hook runs on the recorder/transcoder node and is perfect for uploading the final file to its permanent storage location.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="recorder-configuration-recorderconfigyaml">Recorder Configuration (<code>recorder/config.yaml</code>)<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#recorder-configuration-recorderconfigyaml" class="hash-link" aria-label="Direct link to recorder-configuration-recorderconfigyaml" title="Direct link to recorder-configuration-recorderconfigyaml" translate="no">​</a></h4>
<div class="language-yaml codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-yaml codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">hooks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">post_transcoding</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">scripts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">script</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/path/to/plugnmeet-hooks/post-transcoding-upload.js"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">is_one_shot</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">false</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="the-post-transcoding-upload-script-post-transcoding-uploadjs">The Post-Transcoding Upload Script (<code>post-transcoding-upload.js</code>)<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#the-post-transcoding-upload-script-post-transcoding-uploadjs" class="hash-link" aria-label="Direct link to the-post-transcoding-upload-script-post-transcoding-uploadjs" title="Direct link to the-post-transcoding-upload-script-post-transcoding-uploadjs" translate="no">​</a></h4>
<p>This script runs after a recording has been successfully transcoded to MP4. It receives the path to the final MP4 on the local disk via <code>input_path</code>, uploads it to a permanent S3 bucket, and returns the S3 object key in <code>output_path</code> for the server to store.</p>
<div class="language-javascript codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-javascript codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token hashbang comment" style="color:#999988;font-style:italic">#!/usr/bin/env node</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">S3Client</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">PutObjectCommand</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@aws-sdk/client-s3"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> createReadStream</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> unlinkSync </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fs"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">readline</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"readline"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> s3Client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">S3Client</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">region</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"us-east-1"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-plugnmeet-recordings"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Use a dedicated bucket for recordings</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">log</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">PostTranscoding: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> rl </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> readline</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">createInterface</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">input</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdin</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">terminal</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Post-transcoding upload hook started."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">rl</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"line"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">parse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">line</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Received request: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation known-class-name class-name">JSON</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation method function property-access" style="color:#d73a49">stringify</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation">request</span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// In the post_transcoding stage, `input_path` contains the path to the final MP4 on the local disk.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> input_path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> file_name </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Use the canonical file_name from the hook data to construct the final S3 key.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> final_s3_key </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">recordings/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">file_name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> command </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">PutObjectCommand</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Bucket</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BUCKET_NAME</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> final_s3_key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Body</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createReadStream</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input_path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">ContentType</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"video/mp4"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> s3Client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">command</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Successfully uploaded to </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">final_s3_key</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// This script is responsible for cleaning up the local MP4 file it was given.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">unlinkSync</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input_path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Cleaned up local transcoded file: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">input_path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cleanupError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">WARN: Failed to clean up local file: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">cleanupError</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Set the final S3 key in `output_path` for the server to store in the database.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">output_path</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> final_s3_key</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">ERROR: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> errorResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">output_path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">JSON parse error: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">e</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">stdout</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">errorResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_uXJb alert alert--warning"><div class="admonitionHeading_OYp7"><span class="admonitionIcon_OJue"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Server-Side Compatibility is Required</div><div class="admonitionContent__NE8"><p>The final <code>output_path</code> from this <code>post_transcoding</code> hook (e.g., <code>recordings/REC_123.mp4</code>) is sent to the <code>plugNmeet-server</code> and stored in the database.</p><p>When a user requests to download this recording, the <strong>server</strong> will use its own <code>download_hook</code> with this path as the <code>input_path</code>. You must ensure that your <code>server</code>'s <code>download_hook</code> (like the one shown in the first example) is capable of understanding and processing the <code>output_path</code> format generated by this script.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="get-started-today">Get Started Today<a href="https://www.plugnmeet.org/blog/2026/06/19/mastering-media-workflows-with-hooks#get-started-today" class="hash-link" aria-label="Direct link to Get Started Today" title="Direct link to Get Started Today" translate="no">​</a></h3>
<p>This new hooking system opens up a world of possibilities for automation and advanced, cloud-native architectures. It’s designed for developers and system administrators who want to build a truly custom, scalable, and robust video conferencing platform.</p>
<p>Ready to dive in? Check out our new, comprehensive <strong><a class="" href="https://www.plugnmeet.org/docs/others/hooks">Scripting &amp; Storage Hooks Documentation</a></strong> for detailed technical specifications, payload structures, and more examples.</p>
<p>We can't wait to see the incredible workflows you build!</p>
<hr>
<p><strong>Ready to build your own video conferencing platform?</strong></p>
<ul>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/installation">Follow our Installation Guide</a> to get your self-hosted server running in minutes.</strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a> to explore the features.</strong></li>
</ul>]]></content:encoded>
            <category>plugnmeet</category>
            <category>developer</category>
            <category>hooks</category>
            <category>scripting</category>
            <category>storage</category>
            <category>automation</category>
            <category>s3</category>
            <category>customisation</category>
            <category>architecture</category>
        </item>
        <item>
            <title><![CDATA[Your Website, Your Meetings: Introducing the All-New plugNmeet Plugin for WordPress!]]></title>
            <link>https://www.plugnmeet.org/blog/new-plugnmeet-wordpress-plugin-deep-integration</link>
            <guid>https://www.plugnmeet.org/blog/new-plugnmeet-wordpress-plugin-deep-integration</guid>
            <pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Discover the new plugNmeet plugin for WordPress. Featuring deep integration, shortcode embedding, advanced analytics, and a seamless, white-label experience for self-hosted video conferencing.]]></description>
            <content:encoded><![CDATA[<p>Embedding a video meeting on your WordPress site shouldn't mean sending your users to a third-party platform with confusing branding and a clunky interface. Your website is your brand's home, and your meetings should feel like a natural part of it. What if you could host secure, feature-rich video conferences directly from your own domain, with your own branding, and without writing a single line of code?</p>
<p>Today, we are excited to launch the all-new, deeply integrated plugNmeet plugin for WordPress. This plugin is your gateway to transforming any WordPress page or post into a powerful, self-hosted video conferencing room.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="more-than-just-a-meetingits-part-of-your-site">More Than Just a Meeting—It's Part of Your Site<a href="https://www.plugnmeet.org/blog/new-plugnmeet-wordpress-plugin-deep-integration#more-than-just-a-meetingits-part-of-your-site" class="hash-link" aria-label="Direct link to More Than Just a Meeting—It's Part of Your Site" title="Direct link to More Than Just a Meeting—It's Part of Your Site" translate="no">​</a></h3>
<p>While our original plugin allowed you to <a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-service-with-wordpress">launch your own no-code video conferencing service</a>, this new version is a major leap forward, rebuilt from the ground up for an even deeper and more seamless integration. We focused on a user experience that feels like a native part of WordPress, both for you as the administrator and for your site visitors. From creating rooms in the backend to managing post-session artifacts, every tool you need is right where you'd expect it—inside your WordPress dashboard.</p>
<p>Let's explore the features that make this plugin the ultimate solution for self-hosted video conferencing on WordPress.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="seamless-integration-and-powerful-management">Seamless Integration and Powerful Management<a href="https://www.plugnmeet.org/blog/new-plugnmeet-wordpress-plugin-deep-integration#seamless-integration-and-powerful-management" class="hash-link" aria-label="Direct link to Seamless Integration and Powerful Management" title="Direct link to Seamless Integration and Powerful Management" translate="no">​</a></h3>
<p>We built this plugin to be incredibly easy to use while offering the powerful features our users love.</p>
<ul>
<li class=""><strong>Embed Anywhere with Shortcodes:</strong> Once you create a room, the plugin gives you a simple shortcode (e.g., <code>[plugnmeet_room_view id="your_room_id"]</code>). Paste it into any page, post, or even a widget, and you have a live meeting room. It works flawlessly with the classic editor, Gutenberg, and popular page builders.</li>
<li class=""><strong>Clean, Shareable URLs:</strong> Want a clean, professional link to share for your meetings? The plugin allows you to set a "Host Page," giving every room its own dedicated URL like <code>yoursite.com/meetings/your-room-id/</code>. This is perfect for direct sharing in emails or social media.</li>
<li class=""><strong>Full Backend Control:</strong> Manage everything from your WordPress admin area. The <strong>Plug-N-Meet &gt; Rooms</strong> page is your command center to create, edit, and configure rooms, as well as access all post-session assets.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="effortless-recording-management-with-merging">Effortless Recording Management with Merging<a href="https://www.plugnmeet.org/blog/new-plugnmeet-wordpress-plugin-deep-integration#effortless-recording-management-with-merging" class="hash-link" aria-label="Direct link to Effortless Recording Management with Merging" title="Direct link to Effortless Recording Management with Merging" translate="no">​</a></h3>
<p>Managing your session recordings is now more powerful than ever. If a session was stopped and restarted, you no longer have to deal with multiple video files. Our <strong>new Merge Recordings feature</strong> allows you to select multiple video segments and combine them into a single, continuous MP4 file directly from the WordPress backend. It’s a simple, intuitive way to create a polished, final version of your recording for distribution.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-new-artifacts-menu-analytics-and-ai-at-your-fingertips">The New Artifacts Menu: Analytics and AI at Your Fingertips<a href="https://www.plugnmeet.org/blog/new-plugnmeet-wordpress-plugin-deep-integration#the-new-artifacts-menu-analytics-and-ai-at-your-fingertips" class="hash-link" aria-label="Direct link to The New Artifacts Menu: Analytics and AI at Your Fingertips" title="Direct link to The New Artifacts Menu: Analytics and AI at Your Fingertips" translate="no">​</a></h3>
<p>We've introduced a dedicated <strong>Artifacts menu</strong> for each room, centralizing all the valuable data your sessions produce. This is where you'll find:</p>
<ul>
<li class=""><strong>Actionable Classroom Analytics:</strong> Go beyond simple participant counts. Download comprehensive Excel reports detailing participant engagement, talk time, connection quality, and more. Use this data to understand your audience and improve your presentations, turning raw numbers into pedagogical insights. For a deeper dive, see our guide on <a class="" href="https://www.plugnmeet.org/blog/classroom-analytics-for-educators">Classroom Analytics for Educators</a>.</li>
<li class=""><strong>AI-Powered Summaries &amp; Transcripts:</strong> Access and download meeting summaries and full speech-to-text transcription files generated by our AI Meeting Assistant. For more on our AI capabilities, see our guide on <a class="" href="https://www.plugnmeet.org/blog/how-to-add-ai-meeting-assistant-features">how to add an AI meeting assistant</a>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="built-for-customization-and-control">Built for Customization and Control<a href="https://www.plugnmeet.org/blog/new-plugnmeet-wordpress-plugin-deep-integration#built-for-customization-and-control" class="hash-link" aria-label="Direct link to Built for Customization and Control" title="Direct link to Built for Customization and Control" translate="no">​</a></h3>
<p>We believe in giving you full control over your platform.</p>
<ul>
<li class=""><strong>100% White-Label Experience:</strong> The meeting room loads directly within your WordPress site's design. There are no third-party logos or branding. It's your meeting, on your website. For a deeper dive into customization, read our <a class="" href="https://www.plugnmeet.org/blog/deep-integration-white-label-guide">deep integration and white-label guide</a>.</li>
<li class=""><strong>Granular, Role-Based Permissions:</strong> The plugin leverages standard WordPress user roles to give you fine-grained control. In the room settings, you can define exactly which roles can join as moderators versus attendees, and who gets access to view, download, or manage recordings.</li>
<li class=""><strong>Theme Developer Friendly:</strong> For those who need to customize the front-end display, the plugin features a powerful template overriding system. Developers can copy template files from the plugin into their own theme directory to safely modify the HTML structure, from minor tweaks to a complete redesign.</li>
<li class=""><strong>Extend with Webhooks:</strong> The plugin fires a <code>plugnmeet_webhook_data</code> action hook, allowing developers to easily process real-time meeting data. Use it to build custom logging, send data to a CRM, or create unique workflows. Learn more in our <a class="" href="https://www.plugnmeet.org/blog/how-to-build-your-first-webhook-workflow">guide to building workflows with webhooks</a>.</li>
</ul>
<hr>
<p><strong>Ready to bring your meetings home to your WordPress site?</strong></p>
<ul>
<li class=""><strong><a href="https://wordpress.org/plugins/plugnmeet/" target="_blank" rel="noopener noreferrer" class="">Download the official WordPress Plugin</a></strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a> to explore the features.</strong></li>
<li class=""><strong>Follow our <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a> to get your self-hosted server running in minutes.</strong></li>
</ul>]]></content:encoded>
            <category>wordpress</category>
            <category>plugnmeet</category>
            <category>video conferencing</category>
            <category>wordpress-plugin</category>
            <category>wordpress-integration</category>
            <category>self-hosted</category>
            <category>open-source</category>
            <category>video-meetings</category>
            <category>webinar-plugin</category>
        </item>
        <item>
            <title><![CDATA[Beyond the Broadcast: Building a Custom Q&A Webinar with RTMP and Plug-N-Meet's Broadcast API]]></title>
            <link>https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api</link>
            <guid>https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api</guid>
            <pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Hosting a professional webinar often means balancing a high-quality broadcast with meaningful audience interaction. While RTMP streaming delivers a polished, one-to-many presentation, managing audience questions in a large-scale event can quickly become chaotic. How do you ensure every valuable question is seen, moderated, and delivered to your host without disrupting the flow?]]></description>
            <content:encoded><![CDATA[<p>Hosting a professional webinar often means balancing a high-quality broadcast with meaningful audience interaction. While RTMP streaming delivers a polished, one-to-many presentation, managing audience questions in a large-scale event can quickly become chaotic. How do you ensure every valuable question is seen, moderated, and delivered to your host without disrupting the flow?</p>
<p>Plug-N-Meet's API-first approach, combined with its robust RTMP broadcasting capabilities and the new <code>room/broadcastToRoom</code> API, provides the perfect solution. This guide will show you how to build a custom, curated Q&amp;A system that allows your audience to submit questions through your own platform, which are then seamlessly delivered to your webinar host in real-time.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-power-of-rtmp-broadcasting-for-professional-webinars">The Power of RTMP Broadcasting for Professional Webinars<a href="https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api#the-power-of-rtmp-broadcasting-for-professional-webinars" class="hash-link" aria-label="Direct link to The Power of RTMP Broadcasting for Professional Webinars" title="Direct link to The Power of RTMP Broadcasting for Professional Webinars" translate="no">​</a></h3>
<p>For webinars that demand high production value and reach a large audience, RTMP (Real-Time Messaging Protocol) broadcasting is the gold standard. It allows you to broadcast your Plug-N-Meet session directly to platforms like YouTube, Facebook, or your own website. This session, which includes all participants, screen shares, and whiteboards, becomes your "broadcast studio," ensuring a high-quality experience for thousands of viewers.</p>
<p>This "Broadcast Studio Model" is ideal for <a class="" href="https://www.plugnmeet.org/blog/hosting-large-scale-events-the-smart-way">hosting large-scale events</a>. The meeting itself is private to your hosts and presenters, but the output is streamed publicly.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-qa-dilemma-why-standard-chat-falls-short">The Q&amp;A Dilemma: Why Standard Chat Falls Short<a href="https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api#the-qa-dilemma-why-standard-chat-falls-short" class="hash-link" aria-label="Direct link to The Q&amp;A Dilemma: Why Standard Chat Falls Short" title="Direct link to The Q&amp;A Dilemma: Why Standard Chat Falls Short" translate="no">​</a></h3>
<p>In a large webinar, the public chat can quickly become a firehose of comments, greetings, and questions. While great for general interaction, it's a poor tool for managing a structured Q&amp;A session. Important questions get buried, and the host can easily miss critical inquiries.</p>
<p>A custom Q&amp;A system, integrated directly into your website or application, offers several advantages:</p>
<ul>
<li class=""><strong>Moderation:</strong> Questions can be reviewed and approved before reaching the host.</li>
<li class=""><strong>Prioritization:</strong> Important questions can be highlighted or reordered.</li>
<li class=""><strong>Branding:</strong> The Q&amp;A experience is seamlessly integrated into your platform.</li>
<li class=""><strong>Control:</strong> You dictate the flow and presentation of questions.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="building-your-custom-qa-system-with-roombroadcasttoroom">Building Your Custom Q&amp;A System with <code>room/broadcastToRoom</code><a href="https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api#building-your-custom-qa-system-with-roombroadcasttoroom" class="hash-link" aria-label="Direct link to building-your-custom-qa-system-with-roombroadcasttoroom" title="Direct link to building-your-custom-qa-system-with-roombroadcasttoroom" translate="no">​</a></h3>
<p>This is where the new <code>room/broadcastToRoom</code> API becomes invaluable. It allows your custom backend system to send messages directly into an active Plug-N-Meet room, appearing as if they came from a participant.</p>
<p>Here's the concept:</p>
<ol>
<li class=""><strong>Audience Submission:</strong> Your audience watches the RTMP stream on your website. On the same page (or a linked one), they see a custom form to submit questions.</li>
<li class=""><strong>Backend Moderation:</strong> These questions are sent to <em>your</em> custom backend, where a moderator can review, edit, or approve them.</li>
<li class=""><strong>API Injection:</strong> Once a question is approved, your backend uses the <code>room/broadcastToRoom</code> API to send it as a chat message directly to the host (or all admins) within the Plug-N-Meet broadcast studio room.</li>
</ol>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="how-the-roombroadcasttoroom-api-works-for-qa">How the <code>room/broadcastToRoom</code> API Works for Q&amp;A<a href="https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api#how-the-roombroadcasttoroom-api-works-for-qa" class="hash-link" aria-label="Direct link to how-the-roombroadcasttoroom-api-works-for-qa" title="Direct link to how-the-roombroadcasttoroom-api-works-for-qa" translate="no">​</a></h4>
<p>The <code>room/broadcastToRoom</code> API allows you to send either a <code>chat_msg</code> or a <code>notification_msg</code>. For Q&amp;A, we'll use the <code>chat_msg</code> payload. You can target specific users (like your host) or all admins.</p>
<p><strong>Endpoint:</strong> <code>/room/broadcastToRoom</code></p>
<p><strong>Key Parameters for Q&amp;A:</strong></p>
<ul>
<li class=""><code>room_id</code>: The ID of your Plug-N-Meet broadcast studio room.</li>
<li class=""><code>only_to_admins</code>: Set to <code>true</code> to ensure only your host(s) and co-hosts see the questions.</li>
<li class=""><code>chat_msg.message</code>: The actual question text.</li>
<li class=""><code>chat_msg.to_user_id</code> (Optional): If you want to send the question to a specific host, you can use their <code>user_id</code>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="step-by-step-implementation-idea">Step-by-Step Implementation Idea<a href="https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api#step-by-step-implementation-idea" class="hash-link" aria-label="Direct link to Step-by-Step Implementation Idea" title="Direct link to Step-by-Step Implementation Idea" translate="no">​</a></h3>
<p>Let's outline how you can set this up:</p>
<p><strong>Step 1: Set up Your Plug-N-Meet Webinar Room</strong>
Create your Plug-N-Meet room via the <a class="" href="https://www.plugnmeet.org/docs/api/room/create">Create Room API</a>, ensuring RTMP is enabled. This will be your private "broadcast studio" where your presenters and production team will gather.</p>
<p><strong>Step 2: Configure RTMP Broadcast</strong>
To broadcast your webinar to a public audience, the moderator can initiate the live stream from within the Plug-N-Meet room.</p>
<ol>
<li class="">Open the <strong>More Options</strong> menu (...) in the footer control bar and select <strong>Start Live Stream</strong>.</li>
<li class="">Enter the <strong>Stream Key</strong> and <strong>Stream URL</strong> provided by your streaming platform (e.g., YouTube, Facebook).</li>
<li class="">Click <strong>Start Streaming</strong> to begin broadcasting the session.</li>
</ol>
<p><strong>Step 3: Develop Your Custom Q&amp;A Frontend (Audience-Facing)</strong>
Create a simple web form on your website where your audience can type and submit their questions. This form will send the questions to <em>your</em> custom backend server.</p>
<p><strong>Step 4: Implement Your Q&amp;A Backend (Moderation &amp; API Call)</strong>
This is the core of your custom system:</p>
<ol>
<li class=""><strong>Receive Questions:</strong> Your backend receives questions from the audience-facing form.</li>
<li class=""><strong>Moderation Dashboard:</strong> Build a simple dashboard for your Q&amp;A moderator. Here, they can see incoming questions, approve/reject them, and perhaps reorder them.</li>
<li class=""><strong>Call <code>room/broadcastToRoom</code>:</strong> When the moderator approves a question, your backend makes an API call to Plug-N-Meet's <code>room/broadcastToRoom</code> endpoint.</li>
</ol>
<p><strong>Example JSON Payload for your Backend's API Call to Plug-N-Meet:</strong></p>
<div class="language-json codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-json codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"room_id"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"your-webinar-studio-room-id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"only_to_admins"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"chat_msg"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"message"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Audience Question: [Approved Question Text Here]"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>This will deliver the approved question directly into the chat panel of your Plug-N-Meet broadcast studio, visible only to your host(s) and admins. Your host can then read the question aloud or respond as appropriate.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="benefits-of-this-approach">Benefits of this Approach<a href="https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api#benefits-of-this-approach" class="hash-link" aria-label="Direct link to Benefits of this Approach" title="Direct link to Benefits of this Approach" translate="no">​</a></h3>
<ul>
<li class=""><strong>Curated Q&amp;A:</strong> Only approved questions reach the host, maintaining professionalism.</li>
<li class=""><strong>Reduced Distraction:</strong> The host can focus on the presentation without a constantly scrolling public chat.</li>
<li class=""><strong>Enhanced Control:</strong> You have full control over the Q&amp;A process, from submission to delivery.</li>
<li class=""><strong>Branded Experience:</strong> The audience interacts with your custom platform, reinforcing your brand.</li>
<li class=""><strong>Flexibility:</strong> You can extend your custom Q&amp;A system with features like upvoting, categorization, or even AI-powered question clustering.</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-your-webinar-your-rules">Conclusion: Your Webinar, Your Rules<a href="https://www.plugnmeet.org/blog/custom-qna-webinar-rtmp-broadcast-api#conclusion-your-webinar-your-rules" class="hash-link" aria-label="Direct link to Conclusion: Your Webinar, Your Rules" title="Direct link to Conclusion: Your Webinar, Your Rules" translate="no">​</a></h3>
<p>Plug-N-Meet's API-first architecture empowers you to go beyond off-the-shelf solutions and design truly bespoke webinar experiences. By combining professional RTMP broadcasting with a custom Q&amp;A system powered by the <code>room/broadcastToRoom</code> API, you can deliver highly engaging, moderated, and branded webinars that stand out.</p>
<p>Stop letting generic tools dictate your audience interaction. Start building a webinar experience that's uniquely yours.</p>
<hr>
<p><strong>Ready to build your custom Q&amp;A webinar?</strong></p>
<ul>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/api/room/broadcast-to-room">Explore the <code>room/broadcastToRoom</code> API Documentation</a></strong></li>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/blog/hosting-large-scale-events-the-smart-way">Discover how to host large-scale events</a></strong></li>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/api/intro">Check out our API Documentation to start building</a>.</strong></li>
</ul>]]></content:encoded>
            <category>webinar</category>
            <category>rtmp</category>
            <category>broadcast</category>
            <category>api</category>
            <category>custom-qna</category>
            <category>integration</category>
            <category>developer</category>
            <category>live-streaming</category>
            <category>engagement</category>
            <category>open-source</category>
        </item>
        <item>
            <title><![CDATA[Streamline Your Session Replays: Introducing the New Merge Recordings API]]></title>
            <link>https://www.plugnmeet.org/blog/merge-recordings-api</link>
            <guid>https://www.plugnmeet.org/blog/merge-recordings-api</guid>
            <pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[At Plug-N-Meet, we are constantly working to improve the post-session experience for both administrators and end-users. A common challenge in long-running events like webinars, online classes, or multi-part training sessions is managing multiple recording segments. Stopping and starting a recording is often necessary, but it leaves you with a fragmented collection of video files.]]></description>
            <content:encoded><![CDATA[<p>At Plug-N-Meet, we are constantly working to improve the post-session experience for both administrators and end-users. A common challenge in long-running events like webinars, online classes, or multi-part training sessions is managing multiple recording segments. Stopping and starting a recording is often necessary, but it leaves you with a fragmented collection of video files.</p>
<p>Today, we're excited to solve this problem with the introduction of the <code>/recording/mergeRecordings</code> API—a powerful new tool designed to consolidate multiple recording files into a single, seamless video. This feature is a must-have for anyone running a self-hosted <strong>video conference server</strong> and looking to provide a polished, professional experience.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-problem-fragmented-recordings">The Problem: Fragmented Recordings<a href="https://www.plugnmeet.org/blog/merge-recordings-api#the-problem-fragmented-recordings" class="hash-link" aria-label="Direct link to The Problem: Fragmented Recordings" title="Direct link to The Problem: Fragmented Recordings" translate="no">​</a></h3>
<p>Imagine you're hosting a three-hour workshop on your <strong>video call server</strong>. You pause the recording during a 15-minute break and then resume it. Or perhaps you have to restart a recording due to a technical issue. At the end of the session, you have two or more separate video files.</p>
<p>This creates a disjointed experience for anyone watching the replay. It also creates extra work for administrators who have to manually download, edit, and re-upload the files to create a single, coherent video.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-solution-the-mergerecordings-api">The Solution: The <code>mergeRecordings</code> API<a href="https://www.plugnmeet.org/blog/merge-recordings-api#the-solution-the-mergerecordings-api" class="hash-link" aria-label="Direct link to the-solution-the-mergerecordings-api" title="Direct link to the-solution-the-mergerecordings-api" translate="no">​</a></h3>
<p>The <code>mergeRecordings</code> API automates this entire process. It allows you to programmatically combine multiple recording files from a session into one continuous video. The original files are kept intact, and a new, merged recording is created with its own unique <code>record_id</code>.</p>
<p>This process happens asynchronously in the background, so it doesn’t tie up your system. You simply make the API request, and when the merge is complete, your application receives a webhook notification (<code>recording_proceeded</code>) with the details of the new, unified recording file.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-spotlight-seamless-merging-in-our-moodle-video-conference-plugin">Use Case Spotlight: Seamless Merging in our Moodle Video Conference Plugin<a href="https://www.plugnmeet.org/blog/merge-recordings-api#use-case-spotlight-seamless-merging-in-our-moodle-video-conference-plugin" class="hash-link" aria-label="Direct link to Use Case Spotlight: Seamless Merging in our Moodle Video Conference Plugin" title="Direct link to Use Case Spotlight: Seamless Merging in our Moodle Video Conference Plugin" translate="no">​</a></h3>
<p>This isn't just a theoretical feature—it's already enhancing our official <strong><a class="" href="https://www.plugnmeet.org/docs/user-guide/moodle-integration#merge-multiple-recordings">Plug-N-Meet Moodle Plugin</a></strong>. As a leading <strong>moodle video conference plugin</strong>, we believe in providing seamless workflows for educators.</p>
<p><strong>The Scenario:</strong> A teacher conducts a long class and records it in two parts. After the session, they see both recordings listed in their Moodle activity.</p>
<p><strong>The Old Way:</strong> The teacher would have to instruct students to watch both videos in order, or manually download and edit them together.</p>
<p><strong>The New Way with the API:</strong></p>
<ol>
<li class="">The teacher navigates to the "Recordings" tab in their Moodle course activity.</li>
<li class="">They select the recordings they want to combine.</li>
<li class="">They click a "Merge" button directly within the Moodle interface.</li>
<li class="">The Moodle backend calls the <code>mergeRecordings</code> API.</li>
<li class="">A short time later, a new, single recording file automatically appears in the list, ready for students to view.</li>
</ol>
<p><strong>The Result:</strong> An incredibly simple, intuitive workflow for educators. They can create a polished, single-session replay without ever leaving their Moodle dashboard, demonstrating the power of this API to create user-friendly features on any <strong>video conference server</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="how-it-works-two-powerful-merging-strategies">How It Works: Two Powerful Merging Strategies<a href="https://www.plugnmeet.org/blog/merge-recordings-api#how-it-works-two-powerful-merging-strategies" class="hash-link" aria-label="Direct link to How It Works: Two Powerful Merging Strategies" title="Direct link to How It Works: Two Powerful Merging Strategies" translate="no">​</a></h3>
<p>The API offers two flexible ways to combine your recordings:</p>
<ol>
<li class="">
<p><strong>Merge by Session (<code>by_session</code>)</strong>: This is the simplest method. You provide a <code>room_sid</code> (the unique identifier for a session), and the API automatically finds all recording files associated with that session and merges them in chronological order. You can even choose to exclude specific recordings if you need to remove a false start or an unwanted segment.</p>
</li>
<li class="">
<p><strong>Merge by IDs (<code>by_ids</code>)</strong>: This method gives you granular control. You provide an explicit list of <code>recording_ids</code> in the exact order you want them to be merged. This is perfect for more complex scenarios where you might be combining recordings from different sessions into a single compilation.</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="technical-quick-look">Technical Quick-Look<a href="https://www.plugnmeet.org/blog/merge-recordings-api#technical-quick-look" class="hash-link" aria-label="Direct link to Technical Quick-Look" title="Direct link to Technical Quick-Look" translate="no">​</a></h3>
<ul>
<li class=""><strong>Asynchronous Operation</strong>: This is a background job. Your application makes the API call and gets an immediate confirmation. The actual processing is handled by a <code>plugnmeet-recorder</code> instance.</li>
<li class=""><strong>Webhook Notification</strong>: You must have a webhook endpoint configured to receive the <code>recording_proceeded</code> event. This event will signal that the merged file is ready and provide its new <code>record_id</code>.</li>
<li class=""><strong>Originals are Safe</strong>: The merge process is non-destructive. Your original recording files are never deleted, giving you the freedom to manage them as you see fit.</li>
</ul>
<p>For detailed request and response examples, please refer to the official <strong><a class="" href="https://www.plugnmeet.org/docs/api/recording/merge-recordings">mergeRecordings API Documentation</a></strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-a-smoother-experience-for-everyone">Conclusion: A Smoother Experience for Everyone<a href="https://www.plugnmeet.org/blog/merge-recordings-api#conclusion-a-smoother-experience-for-everyone" class="hash-link" aria-label="Direct link to Conclusion: A Smoother Experience for Everyone" title="Direct link to Conclusion: A Smoother Experience for Everyone" translate="no">​</a></h3>
<p>The <code>mergeRecordings</code> API is a game-changer for anyone who needs to deliver polished, professional session replays. By automating the consolidation of fragmented recordings, it saves administrators time, simplifies post-production workflows, and provides a seamless viewing experience for your audience.</p>
<p>This feature is a perfect example of our commitment to providing a flexible, API-driven platform that can adapt to your unique needs, making your <strong>video call server</strong> more powerful than ever.</p>
<hr>
<p><strong>Ready to simplify your recording management?</strong></p>
<ul>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/api/recording/merge-recordings">Explore the <code>mergeRecordings</code> API Documentation</a></strong></li>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/api/intro">Check out our full API Reference</a></strong></li>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/user-guide/moodle-integration">Learn more about our Moodle Integration</a></strong></li>
</ul>]]></content:encoded>
            <category>api</category>
            <category>developer</category>
            <category>recording</category>
            <category>integration</category>
            <category>automation</category>
            <category>video-editing</category>
            <category>moodle</category>
            <category>use-case</category>
            <category>moodle video conference plugin</category>
            <category>video call server</category>
            <category>video conference server</category>
        </item>
        <item>
            <title><![CDATA[Injecting Content in Real-Time: A Developer's Guide to the New `uploadWhiteboardFile` API]]></title>
            <link>https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api</link>
            <guid>https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api</guid>
            <pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[At Plug-N-Meet, our goal is to provide a platform, not just a product. While our user interface is feature-rich, the true power of our architecture lies in its API-first design. Today, we're excited to introduce a new endpoint that gives developers unprecedented control over live sessions: /room/uploadWhiteboardFile.]]></description>
            <content:encoded><![CDATA[<p>At Plug-N-Meet, our goal is to provide a platform, not just a product. While our user interface is feature-rich, the true power of our architecture lies in its API-first design. Today, we're excited to introduce a new endpoint that gives developers unprecedented control over live sessions: <code>/room/uploadWhiteboardFile</code>.</p>
<p>This API allows your backend application to programmatically upload files directly into an active meeting's whiteboard, opening the door to powerful, seamless, and automated content workflows. This guide will walk you through the key use cases for this new feature and how it can transform your integration.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="why-a-new-upload-api-the-power-of-real-time">Why a New Upload API? The Power of Real-Time<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#why-a-new-upload-api-the-power-of-real-time" class="hash-link" aria-label="Direct link to Why a New Upload API? The Power of Real-Time" title="Direct link to Why a New Upload API? The Power of Real-Time" translate="no">​</a></h3>
<p>You might be familiar with the <code>preload_file</code> option in our <a class="" href="https://www.plugnmeet.org/docs/api/room/create">Create Room API</a>. While useful, it's designed for a different purpose: loading a document <em>before</em> a session begins.</p>
<p>The new <code>uploadWhiteboardFile</code> endpoint is fundamentally different. It's designed to inject content into a <strong>live, running session</strong>. This real-time capability is a game-changer for dynamic applications. When your backend calls this API, the file instantly appears in the presenter's file list within the live meeting, ready to be displayed.</p>
<p>Let's explore what you can build with this.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-1-seamless-cloud-storage-integration-google-drive-dropbox-etc">Use Case 1: Seamless Cloud Storage Integration (Google Drive, Dropbox, etc.)<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#use-case-1-seamless-cloud-storage-integration-google-drive-dropbox-etc" class="hash-link" aria-label="Direct link to Use Case 1: Seamless Cloud Storage Integration (Google Drive, Dropbox, etc.)" title="Direct link to Use Case 1: Seamless Cloud Storage Integration (Google Drive, Dropbox, etc.)" translate="no">​</a></h3>
<p><strong>The Scenario:</strong> A user in your application wants to present a file stored in their Google Drive.</p>
<p><strong>The Old Way:</strong> The user would have to download the file from Google Drive to their computer, then manually upload it through the Plug-N-Meet UI. This is a clunky, multi-step process.</p>
<p><strong>The New Way with the API:</strong></p>
<ol>
<li class="">Your application's UI provides a button: "Present from Google Drive."</li>
<li class="">Using the Google Drive API, you allow the user to select a file.</li>
<li class="">Your backend server securely downloads the file from Google Drive.</li>
<li class="">Your backend then immediately calls the <code>uploadWhiteboardFile</code> API, pushing the file directly into the live Plug-N-Meet session.</li>
</ol>
<p><strong>The Result:</strong> A seamless, professional experience. The user goes from selecting a file in your app to having it ready to present in the meeting in a single click.</p>
<p><strong>Pro-Tip:</strong> For an even more efficient workflow, you can use the <code>document_link</code> parameter. Instead of downloading the file to your server first, you can generate a publicly accessible temporary URL for the file in cloud storage and pass that link to the API. The Plug-N-Meet server will handle the download directly.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-2-centralized-asset-library-for-corporate--education">Use Case 2: Centralized Asset Library for Corporate &amp; Education<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#use-case-2-centralized-asset-library-for-corporate--education" class="hash-link" aria-label="Direct link to Use Case 2: Centralized Asset Library for Corporate &amp; Education" title="Direct link to Use Case 2: Centralized Asset Library for Corporate &amp; Education" translate="no">​</a></h3>
<p><strong>The Scenario:</strong> A company wants to ensure all sales presentations use the latest approved template, or a university wants to provide lecturers with a library of official course materials.</p>
<p><strong>The Old Way:</strong> Presenters would have to find and download the correct file from a separate portal or shared drive before every meeting.</p>
<p><strong>The New Way with the API:</strong></p>
<ol>
<li class="">You build a "Resource Library" directly into your application's dashboard.</li>
<li class="">A presenter, already in a live meeting, can browse this library from your application's interface.</li>
<li class="">When they click "Add to Whiteboard" on a specific asset (e.g., <code>Q3-Sales-Deck.pptx</code>), your backend retrieves the file from its centralized storage.</li>
<li class="">Your backend calls the <code>uploadWhiteboardFile</code> API to inject it into the presenter's current session.</li>
</ol>
<p><strong>The Result:</strong> Perfect brand consistency and effortless access to up-to-date, official materials, all managed from a single source of truth.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-3-dynamic-report-generation-and-on-the-fly-analysis">Use Case 3: Dynamic Report Generation and On-the-Fly Analysis<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#use-case-3-dynamic-report-generation-and-on-the-fly-analysis" class="hash-link" aria-label="Direct link to Use Case 3: Dynamic Report Generation and On-the-Fly Analysis" title="Direct link to Use Case 3: Dynamic Report Generation and On-the-Fly Analysis" translate="no">​</a></h3>
<p><strong>The Scenario:</strong> A team is in a live meeting discussing performance metrics. They realize they need to see a specific, up-to-the-minute report that doesn't exist yet.</p>
<p><strong>The Old Way:</strong> Someone would have to leave the meeting, generate the report, save it, and then manually upload it. The meeting's momentum is lost.</p>
<p><strong>The New Way with the API:</strong></p>
<ol>
<li class="">Your application has a "Generate Live Report" button integrated into its dashboard.</li>
<li class="">A user in the meeting clicks the button and selects the required parameters.</li>
<li class="">Your backend generates the report (e.g., a PDF of a graph) on the fly.</li>
<li class="">As soon as the file is created, your backend calls the <code>uploadWhiteboardFile</code> API.</li>
</ol>
<p><strong>The Result:</strong> A report is generated and delivered into the live meeting in seconds, enabling true real-time, data-driven decision-making.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-4-moodle-integration---bridging-content-and-classroom">Use Case 4: Moodle Integration - Bridging Content and Classroom<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#use-case-4-moodle-integration---bridging-content-and-classroom" class="hash-link" aria-label="Direct link to Use Case 4: Moodle Integration - Bridging Content and Classroom" title="Direct link to Use Case 4: Moodle Integration - Bridging Content and Classroom" translate="no">​</a></h3>
<p><strong>The Scenario:</strong> Our official <a class="" href="https://www.plugnmeet.org/docs/user-guide/moodle-integration#upload-to-whiteboard-from-moodle">Plug-N-Meet Moodle Plugin</a> already leverages this API to create a seamless experience for educators. Teachers can manage a central repository of course materials within Moodle.</p>
<p><strong>The New Way with the API:</strong></p>
<ol>
<li class="">A teacher is conducting a live class using the Plug-N-Meet activity in Moodle.</li>
<li class="">They decide they need to show a specific PDF or presentation from their Moodle course files.</li>
<li class="">From an interface within Moodle (outside the live session window), the teacher selects the file.</li>
<li class="">The Moodle backend calls the <code>uploadWhiteboardFile</code> API, instantly pushing the selected document into the live virtual classroom.</li>
</ol>
<p><strong>The Result:</strong> The teacher never has to leave their Moodle environment. They can inject course materials into the live session on the fly, creating a fluid and uninterrupted teaching experience. This showcases how the API can bridge an external content management system directly with a live session.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="technical-quick-look--important-considerations">Technical Quick-Look &amp; Important Considerations<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#technical-quick-look--important-considerations" class="hash-link" aria-label="Direct link to Technical Quick-Look &amp; Important Considerations" title="Direct link to Technical Quick-Look &amp; Important Considerations" translate="no">​</a></h3>
<p>This endpoint uses a <code>multipart/form-data</code> request, which requires a different authentication method than our standard JSON APIs.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="authentication">Authentication<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#authentication" class="hash-link" aria-label="Direct link to Authentication" title="Direct link to Authentication" translate="no">​</a></h4>
<p>The <code>HASH-SIGNATURE</code> must be generated using your <code>API-SECRET</code> with the value of the <code>Room-Id</code> header as the message body. This is different from other APIs where the signature is generated from the request body.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="file-constraints">File Constraints<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#file-constraints" class="hash-link" aria-label="Direct link to File Constraints" title="Direct link to File Constraints" translate="no">​</a></h4>
<ul>
<li class=""><strong>Supported File Types</strong>: This API is optimized for office documents and presentations (<code>PDF</code>, <code>PPT</code>, <code>DOC</code>, etc.). Image files like <code>JPG</code> or <code>PNG</code> are <strong>not</strong> supported and will be rejected.</li>
<li class=""><strong>File Size</strong>: The file must not exceed the <code>max_size_whiteboard_file</code> limit defined in your server's configuration.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="server-behavior">Server Behavior<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#server-behavior" class="hash-link" aria-label="Direct link to Server Behavior" title="Direct link to Server Behavior" translate="no">​</a></h4>
<ul>
<li class=""><strong>Concurrent Uploads</strong>: The server processes only one upload at a time per room. If you send a new upload request while another is in progress for the same room, you will receive a <code>409 Conflict</code> error.</li>
<li class=""><strong>Timeouts &amp; Background Processing</strong>: File conversion can be resource-intensive. If your initial API call times out, don't worry. The server continues to process the file in the background. Once complete, the file will appear in the room, and the upload lock will be released.</li>
</ul>
<p>For a complete <code>curl</code> example and detailed parameter descriptions, please refer to the official <strong><a class="" href="https://www.plugnmeet.org/docs/api/room/upload-whiteboard-file">uploadWhiteboardFile API Documentation</a></strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-your-application-is-now-the-co-presenter">Conclusion: Your Application is Now the Co-Presenter<a href="https://www.plugnmeet.org/blog/programmatic-whiteboard-uploads-api#conclusion-your-application-is-now-the-co-presenter" class="hash-link" aria-label="Direct link to Conclusion: Your Application is Now the Co-Presenter" title="Direct link to Conclusion: Your Application is Now the Co-Presenter" translate="no">​</a></h3>
<p>The <code>uploadWhiteboardFile</code> API transforms the whiteboard from a simple user-driven tool into a programmable, dynamic canvas. It allows your application to become an active participant in the meeting, delivering relevant content exactly when it's needed.</p>
<p>This feature deepens the potential for integration, enabling you to build smarter, more automated, and highly professional workflows that set your application apart.</p>
<hr>
<p><strong>Ready to start building?</strong></p>
<ul>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/api/room/upload-whiteboard-file">Explore the <code>uploadWhiteboardFile</code> API Documentation</a></strong></li>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/api/intro">Check out our full API Reference</a></strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try our Live Demo</a> to see the whiteboard in action.</strong></li>
</ul>]]></content:encoded>
            <category>api</category>
            <category>developer</category>
            <category>whiteboard</category>
            <category>integration</category>
            <category>automation</category>
            <category>real-time</category>
            <category>file-upload</category>
            <category>use-case</category>
            <category>moodle</category>
            <category>moodle video conference plugin</category>
            <category>video call server</category>
            <category>video conference server</category>
        </item>
        <item>
            <title><![CDATA[Beyond the Presentation: Designing Interactive Webinars with Plug-N-Meet]]></title>
            <link>https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet</link>
            <guid>https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet</guid>
            <pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We've all been there: a webinar that feels less like a live event and more like a glorified slideshow. The presenter talks at the audience, the chat scrolls by unnoticed, and engagement dwindles. In today's digital-first world, simply broadcasting information isn't enough. Your audience expects to participate, to contribute, and to feel like an active part of the experience.]]></description>
            <content:encoded><![CDATA[<p>We've all been there: a webinar that feels less like a live event and more like a glorified slideshow. The presenter talks <em>at</em> the audience, the chat scrolls by unnoticed, and engagement dwindles. In today's digital-first world, simply broadcasting information isn't enough. Your audience expects to participate, to contribute, and to feel like an active part of the experience.</p>
<p>At Plug-N-Meet, we believe webinars should be dynamic, engaging, and truly interactive. It's not about replacing your presentation, but enhancing it with tools that foster real-time collaboration and feedback. This guide will show you how to leverage Plug-N-Meet's powerful features to design webinars that captivate your audience and deliver measurable results.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-problem-with-passive-webinars">The Problem with Passive Webinars<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#the-problem-with-passive-webinars" class="hash-link" aria-label="Direct link to The Problem with Passive Webinars" title="Direct link to The Problem with Passive Webinars" translate="no">​</a></h3>
<p>Traditional webinars often fall short because they treat the audience as passive consumers. This leads to:</p>
<ul>
<li class=""><strong>Low Engagement:</strong> Attendees tune out, multitask, or simply leave.</li>
<li class=""><strong>Missed Opportunities:</strong> Valuable insights and questions from the audience are lost.</li>
<li class=""><strong>Ineffective Learning:</strong> Information retention is poor without active participation.</li>
<li class=""><strong>Generic Experience:</strong> Your event feels indistinguishable from countless others.</li>
</ul>
<p>Plug-N-Meet empowers you to break free from this mold and create memorable, impactful webinars.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="plug-n-meets-interactive-toolkit">Plug-N-Meet's Interactive Toolkit<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#plug-n-meets-interactive-toolkit" class="hash-link" aria-label="Direct link to Plug-N-Meet's Interactive Toolkit" title="Direct link to Plug-N-Meet's Interactive Toolkit" translate="no">​</a></h3>
<p>Our platform is packed with features designed to turn your audience into active participants. Here’s how you can use them:</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="1-live-polls-instant-feedback--decision-making">1. Live Polls: Instant Feedback &amp; Decision Making<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#1-live-polls-instant-feedback--decision-making" class="hash-link" aria-label="Direct link to 1. Live Polls: Instant Feedback &amp; Decision Making" title="Direct link to 1. Live Polls: Instant Feedback &amp; Decision Making" translate="no">​</a></h4>
<p>Forget trying to tally responses in a chaotic chat. Live Polls allow you to ask structured questions and get immediate, quantifiable feedback from your entire audience.</p>
<ul>
<li class=""><strong>How to use it:</strong> Launch quick quizzes to check comprehension, gauge opinions on a new product feature, or even make group decisions. You can see results in real-time and publish them for discussion. For more ideas, read our post on <a class="" href="https://www.plugnmeet.org/blog/engaging-audience-with-live-polls">Engaging Audience with Live Polls</a>.</li>
<li class=""><strong>Impact:</strong> Boosts engagement, provides valuable data, and keeps the audience invested in the content.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="2-breakout-rooms-small-group-collaboration-at-scale">2. Breakout Rooms: Small Group Collaboration at Scale<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#2-breakout-rooms-small-group-collaboration-at-scale" class="hash-link" aria-label="Direct link to 2. Breakout Rooms: Small Group Collaboration at Scale" title="Direct link to 2. Breakout Rooms: Small Group Collaboration at Scale" translate="no">​</a></h4>
<p>For workshops, training sessions, or brainstorming, small group discussions are invaluable. Breakout Rooms allow you to split your large audience into smaller, private sub-meetings.</p>
<ul>
<li class=""><strong>How to use it:</strong> Assign participants randomly or manually to groups. Each room gets its own private whiteboard, shared notepad, and full audio/video capabilities. As a moderator, you can broadcast messages to all rooms or join specific groups. Learn more in our guide to <a class="" href="https://www.plugnmeet.org/blog/mastering-breakout-rooms">Mastering Breakout Rooms</a>.</li>
<li class=""><strong>Impact:</strong> Fosters deeper collaboration, encourages quieter participants to speak up, and allows for focused problem-solving.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="3-interactive-whiteboard--shared-notepad-visual--textual-collaboration">3. Interactive Whiteboard &amp; Shared Notepad: Visual &amp; Textual Collaboration<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#3-interactive-whiteboard--shared-notepad-visual--textual-collaboration" class="hash-link" aria-label="Direct link to 3. Interactive Whiteboard &amp; Shared Notepad: Visual &amp; Textual Collaboration" title="Direct link to 3. Interactive Whiteboard &amp; Shared Notepad: Visual &amp; Textual Collaboration" translate="no">​</a></h4>
<p>Move beyond static slides. Our integrated whiteboard and shared notepad provide dynamic canvases for collective ideation and note-taking.</p>
<ul>
<li class=""><strong>How to use it:</strong> Upload presentations or documents to the whiteboard and annotate them in real-time. Enable multi-user drawing for collaborative brainstorming. Use the shared notepad for collective note-taking, drafting ideas, or creating action item lists. Dive deeper into our <a class="" href="https://www.plugnmeet.org/blog/integrated-whiteboard-experience">Integrated Whiteboard Experience</a>.</li>
<li class=""><strong>Impact:</strong> Enhances visual learning, makes abstract concepts clearer, and creates shared artifacts of the session.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="4-qa--raise-hand-structured-audience-interaction">4. Q&amp;A / Raise Hand: Structured Audience Interaction<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#4-qa--raise-hand-structured-audience-interaction" class="hash-link" aria-label="Direct link to 4. Q&amp;A / Raise Hand: Structured Audience Interaction" title="Direct link to 4. Q&amp;A / Raise Hand: Structured Audience Interaction" translate="no">​</a></h4>
<p>Manage questions effectively without interrupting the flow of your presentation.</p>
<ul>
<li class=""><strong>How to use it:</strong> Encourage attendees to use the "Raise Hand" feature to signal they have a question. As a moderator, you can then unmute them individually. Use the public chat for general comments, but direct specific questions to a dedicated Q&amp;A segment. Our <a class="" href="https://www.plugnmeet.org/blog/moderator-guide-room-lock-settings">Moderator Guide to Room Lock Settings</a> provides more details on managing interactions.</li>
<li class=""><strong>Impact:</strong> Maintains order, ensures all questions are addressed, and gives every participant a chance to be heard.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="5-embed-web-content-dynamic-presentations">5. Embed Web Content: Dynamic Presentations<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#5-embed-web-content-dynamic-presentations" class="hash-link" aria-label="Direct link to 5. Embed Web Content: Dynamic Presentations" title="Direct link to 5. Embed Web Content: Dynamic Presentations" translate="no">​</a></h4>
<p>Bring the web directly into your meeting without clunky screen sharing.</p>
<ul>
<li class=""><strong>How to use it:</strong> Share any website, online application (like a Google Doc, Miro board, or project management tool), or interactive content directly within the meeting window. Find out more in <a class="" href="https://www.plugnmeet.org/blog/embed-web-content-dynamic-collaboration">Bring the Web into Your Meeting</a>.</li>
<li class=""><strong>Impact:</strong> Creates a seamless, integrated presentation experience, reduces distractions, and enables real-time collaborative work on external resources.</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="designing-your-interactive-webinar-a-workflow-example">Designing Your Interactive Webinar: A Workflow Example<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#designing-your-interactive-webinar-a-workflow-example" class="hash-link" aria-label="Direct link to Designing Your Interactive Webinar: A Workflow Example" title="Direct link to Designing Your Interactive Webinar: A Workflow Example" translate="no">​</a></h3>
<p>Here’s how you might structure a 60-minute interactive webinar using Plug-N-Meet:</p>
<ul>
<li class="">
<p><strong>Pre-Webinar Setup:</strong></p>
<ul>
<li class=""><strong>Enable Features:</strong> When creating your room (via API or dashboard), ensure <code>polls_features</code>, <code>breakout_room_features</code>, <code>whiteboard_features</code>, <code>shared_note_pad_features</code>, and <code>display_external_link_features</code> are all enabled. You can find detailed API parameters in our <a class="" href="https://www.plugnmeet.org/docs/api/room/create">Create Room API Documentation</a>.</li>
<li class=""><strong>Default Lock Settings:</strong> For a webinar, you might start with microphones and webcams locked for attendees by default to maintain control. Refer to our <a class="" href="https://www.plugnmeet.org/docs/user-guide/moderator">Moderator Guide</a> for more on lock settings.</li>
<li class=""><strong>Webhooks:</strong> Set up a webhook to automate post-webinar tasks like sending recordings or summaries. Learn how in <a class="" href="https://www.plugnmeet.org/blog/how-to-build-your-first-webhook-workflow">How to Build Your First Automated Workflow with Webhooks</a>.</li>
</ul>
</li>
<li class="">
<p><strong>0-5 Minutes: Welcome &amp; Icebreaker (Interactive Whiteboard)</strong></p>
<ul>
<li class="">Start with a simple, engaging activity. Upload a world map to the whiteboard and ask participants to mark their location using the multi-user drawing tool. This gets everyone comfortable with the tools and creates a visual representation of your global audience.</li>
</ul>
</li>
<li class="">
<p><strong>5-20 Minutes: Core Presentation &amp; Quick Check (Live Polls)</strong></p>
<ul>
<li class="">Deliver your initial content. After a key segment, launch a quick poll to check for understanding or gather opinions. "On a scale of 1-5, how relevant is this topic to your work?" Publish the results and briefly discuss.</li>
</ul>
</li>
<li class="">
<p><strong>20-40 Minutes: Collaborative Activity (Breakout Rooms with Whiteboard/Notepad)</strong></p>
<ul>
<li class="">Introduce a problem or discussion topic. Divide your audience into small groups using Breakout Rooms. Instruct them to use their private whiteboards for brainstorming or shared notepads for outlining solutions. Broadcast a "5 minutes left!" message before bringing them back.</li>
</ul>
</li>
<li class="">
<p><strong>40-50 Minutes: Debrief &amp; Q&amp;A (Live Polls &amp; Raise Hand)</strong></p>
<ul>
<li class="">Once back in the main room, launch another poll: "Which solution did your group find most promising?" Publish results. Then, open the floor for questions using the "Raise Hand" feature, unmuting participants one by one.</li>
</ul>
</li>
<li class="">
<p><strong>50-60 Minutes: Dynamic Content &amp; Call to Action (Embed Web Content)</strong></p>
<ul>
<li class="">Instead of just telling people where to go, use "Share External Link" to display your website, a sign-up form, or a relevant resource directly in the meeting. Conclude with a final poll for overall feedback.</li>
</ul>
</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-your-audience-deserves-more">Conclusion: Your Audience Deserves More<a href="https://www.plugnmeet.org/blog/designing-interactive-webinars-with-plugnmeet#conclusion-your-audience-deserves-more" class="hash-link" aria-label="Direct link to Conclusion: Your Audience Deserves More" title="Direct link to Conclusion: Your Audience Deserves More" translate="no">​</a></h3>
<p>Moving beyond the passive presentation transforms your webinars from forgettable broadcasts into memorable, high-impact events. With Plug-N-Meet, you have a comprehensive, open-source toolkit to design truly interactive experiences that engage your audience, foster collaboration, and deliver real value.</p>
<p>Stop talking <em>at</em> your audience. Start working <em>with</em> them.</p>
<hr>
<p><strong>Ready to design your next interactive webinar?</strong></p>
<ul>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try these features in our Live Demo</a></strong></li>
<li class=""><strong><a href="https://github.com/mynaparrot/plugNmeet-server" target="_blank" rel="noopener noreferrer" class="">Explore our Open-Source Project on GitHub</a></strong></li>
<li class=""><strong><a class="" href="https://www.plugnmeet.org/docs/user-guide/moderator">Read our Moderator Guide</a> for more tips on managing interactive sessions.</strong></li>
</ul>]]></content:encoded>
            <category>webinar</category>
            <category>interactive-webinar</category>
            <category>engagement</category>
            <category>polling</category>
            <category>breakout-rooms</category>
            <category>whiteboard</category>
            <category>q&amp;a</category>
            <category>moderator</category>
            <category>features</category>
            <category>open-source</category>
        </item>
        <item>
            <title><![CDATA[5 Ways an AI Assistant Can Transform Your Virtual Classroom]]></title>
            <link>https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom</link>
            <guid>https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom</guid>
            <pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The shift to online learning has opened up incredible opportunities, but it also comes with unique challenges. How do you keep students engaged when they're just another face in a grid? How do you cater to different learning styles and language needs through a screen?]]></description>
            <content:encoded><![CDATA[<p>The shift to online learning has opened up incredible opportunities, but it also comes with unique challenges. How do you keep students engaged when they're just another face in a grid? How do you cater to different learning styles and language needs through a screen?</p>
<p>The answer isn't to work harder; it's to teach smarter. Imagine having a silent partner in every session, one that makes your virtual classroom more inclusive, accessible, and effective. This is the power of an <strong>AI Teaching Assistant</strong>. Here are 5 ways it can revolutionize your online teaching.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="1-make-every-student-feel-included-with-live-translation">1. Make Every Student Feel Included with Live Translation<a href="https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom#1-make-every-student-feel-included-with-live-translation" class="hash-link" aria-label="Direct link to 1. Make Every Student Feel Included with Live Translation" title="Direct link to 1. Make Every Student Feel Included with Live Translation" translate="no">​</a></h3>
<p><strong>The Challenge:</strong> A student’s primary language isn't the one being spoken in class. They struggle to keep up, hesitate to participate, and quickly become disengaged.</p>
<p><strong>The AI Solution:</strong> With a single click, that student can enable live, real-time translation. As you speak, they see captions in their native language, allowing them to understand the material and formulate questions. This simple tool breaks down language barriers and fosters a truly global, inclusive classroom where every student has a voice.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="2-boost-comprehension-for-every-learning-style">2. Boost Comprehension for Every Learning Style<a href="https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom#2-boost-comprehension-for-every-learning-style" class="hash-link" aria-label="Direct link to 2. Boost Comprehension for Every Learning Style" title="Direct link to 2. Boost Comprehension for Every Learning Style" translate="no">​</a></h3>
<p><strong>The Challenge:</strong> Students have diverse learning needs. Some are visual learners, some may be in a noisy environment, and others may have hearing impairments.</p>
<p><strong>The AI Solution:</strong> Live transcription provides real-time captions of everything you say. This reinforces your lesson visually, helps students catch anything they might have missed, and provides a critical accessibility tool. It ensures that all students can follow along, improving focus and knowledge retention for everyone.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="3-get-instant-answers-with-a-built-in-research-assistant">3. Get Instant Answers with a Built-in Research Assistant<a href="https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom#3-get-instant-answers-with-a-built-in-research-assistant" class="hash-link" aria-label="Direct link to 3. Get Instant Answers with a Built-in Research Assistant" title="Direct link to 3. Get Instant Answers with a Built-in Research Assistant" translate="no">​</a></h3>
<p><strong>The Challenge:</strong> You mention a concept or historical figure that a student isn't familiar with. To understand it, the student has to open a new browser tab, search, and read, causing them to miss the next part of the lesson.</p>
<p><strong>The AI Solution:</strong> The AI Assistant acts as a built-in research tool. A student can privately ask the AI general knowledge questions like, "Who was Marie Curie?" or "What is the Pythagorean theorem?" and get a quick, concise answer without ever leaving the meeting window. This helps them fill knowledge gaps instantly and stay focused on the live lesson.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="4-create-perfect-searchable-study-guidesautomatically">4. Create Perfect, Searchable Study Guides—Automatically<a href="https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom#4-create-perfect-searchable-study-guidesautomatically" class="hash-link" aria-label="Direct link to 4. Create Perfect, Searchable Study Guides—Automatically" title="Direct link to 4. Create Perfect, Searchable Study Guides—Automatically" translate="no">​</a></h3>
<p><strong>The Challenge:</strong> After a long lecture, you need to share your notes, and students need a reliable way to review the material.</p>
<p><strong>The AI Solution:</strong> Forget manual note-taking. Once the session is over, the AI Assistant automatically generates a concise summary of the entire lesson, complete with key concepts and action items. This becomes a perfect, searchable study guide that saves both you and your students hours of work.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="5-focus-on-teaching-not-juggling-tasks">5. Focus on Teaching, Not Juggling Tasks<a href="https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom#5-focus-on-teaching-not-juggling-tasks" class="hash-link" aria-label="Direct link to 5. Focus on Teaching, Not Juggling Tasks" title="Direct link to 5. Focus on Teaching, Not Juggling Tasks" translate="no">​</a></h3>
<p><strong>The Challenge:</strong> As an educator, you're juggling multiple roles at once: presenting content, managing participants, monitoring the chat, and trying to take notes.</p>
<p><strong>The AI Solution:</strong> By offloading the tasks of transcription and post-meeting summarization to the AI, you are free to do what you do best: teach. You can focus entirely on student-interaction, gauge understanding, and deliver a more dynamic and engaging lesson.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="your-new-superpower">Your New Superpower<a href="https://www.plugnmeet.org/blog/ai-transforms-virtual-classroom#your-new-superpower" class="hash-link" aria-label="Direct link to Your New Superpower" title="Direct link to Your New Superpower" translate="no">​</a></h2>
<p>AI isn't about replacing great teachers; it's about giving them superpowers. It automates the tedious tasks, breaks down barriers to participation, and allows you to create a more powerful and equitable learning experience for every student.</p>
<p>At plugNmeet, we believe this technology should be simple and accessible for everyone.</p>
<p><strong>For educators</strong>, our platform integrates seamlessly into the tools you already use. Get started in minutes with our guides for <a class="" href="https://www.plugnmeet.org/docs/user-guide/moodle-integration">Moodle</a>, <a class="" href="https://www.plugnmeet.org/docs/user-guide/wordPress-integration">WordPress</a>, or <a class="" href="https://www.plugnmeet.org/docs/user-guide/joomla-integration">Joomla</a>.</p>
<p><strong>For developers</strong>, our flexible, open-source foundation is so powerful you can <a class="" href="https://www.plugnmeet.org/blog/build-video-conferencing-app-in-under-an-hour">build a complete video conferencing app in under an hour</a>.</p>
<p><strong>Ready to see how an AI assistant can transform your classroom?</strong></p>
<ul>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try our Live Demo</a></strong> to experience these features firsthand.</li>
<li class=""><strong>Want to learn how it works?</strong> Read our technical guide on <a class="" href="https://www.plugnmeet.org/blog/how-to-add-ai-meeting-assistant-features">how to add an AI meeting assistant</a>.</li>
</ul>]]></content:encoded>
            <category>ai</category>
            <category>online-classroom</category>
            <category>virtual-classroom</category>
            <category>student-engagement</category>
            <category>ai-for-education</category>
            <category>teaching-assistant</category>
            <category>inclusive-classroom</category>
            <category>online-teaching</category>
        </item>
        <item>
            <title><![CDATA[Deep Integration, Powerful Features: The All-New plugNmeet Plugin for Moodle is Here!]]></title>
            <link>https://www.plugnmeet.org/blog/new-plugnmeet-moodle-plugin-deep-integration</link>
            <guid>https://www.plugnmeet.org/blog/new-plugnmeet-moodle-plugin-deep-integration</guid>
            <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Discover the completely rebuilt plugNmeet plugin for Moodle 4.5+. Featuring deep integration with Moodle's core systems, cumulative attendance, AI artifacts, and a seamless, white-label experience.]]></description>
            <content:encoded><![CDATA[<p>Sending students to an external video conferencing link breaks the learning flow, creates a disjointed experience, and pulls them away from the Moodle environment you've carefully built. What if your virtual classroom wasn't a detour, but a destination—a native part of your course?</p>
<p>Today, we're thrilled to announce the launch of the completely rebuilt plugNmeet plugin for Moodle. This isn't just an update; it's a ground-up redesign focused on one thing: deep, seamless integration that makes your virtual classroom feel like a core Moodle activity.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="a-major-leap-forward">A Major Leap Forward<a href="https://www.plugnmeet.org/blog/new-plugnmeet-moodle-plugin-deep-integration#a-major-leap-forward" class="hash-link" aria-label="Direct link to A Major Leap Forward" title="Direct link to A Major Leap Forward" translate="no">​</a></h3>
<p>While our previous plugin offered basic branding and recording features, this new version is in a different league. We've rebuilt it from scratch with a laser focus on <strong>Moodle 4.5+ standards</strong>, creating a robust, secure, and deeply integrated solution. The user interface is now cleaner and more intuitive, with features logically placed within the Moodle activity context, just as you'd expect.</p>
<p>So, what makes this new version so special? Let's dive into the features that will change the way you teach online.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="more-than-a-pluginits-part-of-moodle">More Than a Plugin—It's Part of Moodle<a href="https://www.plugnmeet.org/blog/new-plugnmeet-moodle-plugin-deep-integration#more-than-a-pluginits-part-of-moodle" class="hash-link" aria-label="Direct link to More Than a Plugin—It's Part of Moodle" title="Direct link to More Than a Plugin—It's Part of Moodle" translate="no">​</a></h3>
<p>We moved beyond simple embedding to deeply integrate plugNmeet into Moodle’s core systems.</p>
<ul>
<li class=""><strong>Advanced Completion Tracking:</strong> Go beyond simple "viewed" metrics. Our plugin introduces <strong>cumulative completion criteria</strong>. Track user engagement across <em>all</em> sessions within a single Moodle activity. Mark the activity complete based on:<!-- -->
<ul>
<li class="">Total minutes attended</li>
<li class="">Total webcam or microphone duration</li>
<li class="">Number of chat messages sent</li>
<li class="">Raised hand, voted in a poll, and more...</li>
</ul>
</li>
<li class=""><strong>Automatic Calendar Integration:</strong> When you schedule a session using the "Available from" date, the plugin automatically creates an event in the Moodle Calendar. Your students will see upcoming classes on their dashboard and course calendars, boosting visibility and attendance.</li>
<li class=""><strong>Gradebook Integration:</strong> Assign a grade for your plugNmeet activity. The plugin can automatically calculate a grade based on the completion progress of the criteria you set.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="transparency-and-data-driven-insights">Transparency and Data-Driven Insights<a href="https://www.plugnmeet.org/blog/new-plugnmeet-moodle-plugin-deep-integration#transparency-and-data-driven-insights" class="hash-link" aria-label="Direct link to Transparency and Data-Driven Insights" title="Direct link to Transparency and Data-Driven Insights" translate="no">​</a></h3>
<p>Knowledge is power—not just for teachers, but for students too. This plugin gives you the data you need to understand engagement like never before. For a deeper dive into how to use this data, see our guide on <a class="" href="https://www.plugnmeet.org/blog/classroom-analytics-for-educators">Classroom Analytics for Educators</a>.</p>
<ul>
<li class=""><strong>New Student Self-Reporting Dashboard:</strong> Students no longer have to wonder about their progress. They now have access to a beautiful, personalized dashboard that shows their cumulative participation data in a modern, card-based layout.</li>
<li class=""><strong>Visual Progress Tracking:</strong> If completion criteria are set, students see color-coded status cards (Green for met, Yellow for pending) with clear badges indicating exactly what they need to do to complete the activity.</li>
<li class=""><strong>Cumulative Attendance Reporting:</strong> Teachers get a consolidated attendance list for the entire activity. See at a glance who is attending and for how long across a series of classes, with the ability to export the data to Excel.</li>
<li class=""><strong>Live Session Dashboard:</strong> Need to know what's happening in a live class right now? Teachers can view a real-time dashboard showing the number of participants, active webcams, microphones, and more.</li>
<li class=""><strong>Session Artifacts in Moodle:</strong> After a session, all generated files—including AI-powered meeting summaries, full transcriptions (VTT files), and detailed analytics reports—are available directly on the Moodle activity page. To learn more about the AI capabilities, read our guide on <a class="" href="https://www.plugnmeet.org/blog/how-to-add-ai-meeting-assistant-features">how to add AI features to your video conferences</a>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="built-for-administrators-and-developers">Built for Administrators and Developers<a href="https://www.plugnmeet.org/blog/new-plugnmeet-moodle-plugin-deep-integration#built-for-administrators-and-developers" class="hash-link" aria-label="Direct link to Built for Administrators and Developers" title="Direct link to Built for Administrators and Developers" translate="no">​</a></h3>
<p>We built this plugin to be as powerful for administrators as it is for teachers.</p>
<ul>
<li class=""><strong>100% White-Label Experience:</strong> Using the <code>Remote (Embedded)</code> client mode, the plugNmeet classroom loads directly within your Moodle site's layout. There are no redirects and no third-party branding. To your users, it’s simply <em>your</em> virtual classroom, powered by Moodle.</li>
<li class=""><strong>Granular, Role-Based Access:</strong> We've implemented a comprehensive set of Moodle capabilities to give you fine-grained control over who can manage rooms, view recordings, download artifacts, and see attendance reports.</li>
<li class=""><strong>Extensible Events API:</strong> The plugin receives real-time data from your plugNmeet server via webhooks and immediately re-broadcasts them as standard Moodle events. This allows developers to easily hook into events like <code>participant_joined</code> or <code>recording_proceeded</code> to build custom integrations and extend functionality.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="a-seamless-experience-for-everyone">A Seamless Experience for Everyone<a href="https://www.plugnmeet.org/blog/new-plugnmeet-moodle-plugin-deep-integration#a-seamless-experience-for-everyone" class="hash-link" aria-label="Direct link to A Seamless Experience for Everyone" title="Direct link to A Seamless Experience for Everyone" translate="no">​</a></h3>
<ul>
<li class=""><strong>Secure Guest Access:</strong> A new, secure workflow allows you to generate time-limited, single-use guest links for guest speakers or external participants, without compromising your site's security. For maximum security in sensitive meetings, you can also enable <a class="" href="https://www.plugnmeet.org/blog/e2ee-key-models-guide">end-to-end encryption</a>.</li>
<li class=""><strong>Redesigned Recordings Page:</strong> Managing cloud recordings is now a breeze. View and control the visibility of your recordings from a clean and intuitive interface within the activity page.</li>
</ul>
<p>To get started, check out our <a class="" href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide">Ultimate Guide to Moodle Video Conferencing</a> or our tutorial on <a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-moodle">How to Add No-Code Video Conferencing to Your Moodle Courses</a>.</p>
<hr>
<p><strong>Ready to enhance your Moodle courses?</strong></p>
<ul>
<li class=""><strong><a href="https://moodle.org/plugins/mod_plugnmeet" target="_blank" rel="noopener noreferrer" class="">Download the official Moodle Plugin</a></strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a> to explore the teaching tools.</strong></li>
<li class=""><strong>Follow our <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a> to get your server running.</strong></li>
</ul>]]></content:encoded>
            <category>moodle</category>
            <category>plugnmeet</category>
            <category>virtual classroom</category>
            <category>e-learning</category>
            <category>video conferencing</category>
            <category>moodle-virtual-classroom</category>
            <category>moodle-plugin</category>
            <category>moodle-integration</category>
            <category>online-learning</category>
            <category>education-technology</category>
        </item>
        <item>
            <title><![CDATA[Unlock True Control: Why Self-Hosted, Open Source Webinar Software is Your Best Bet]]></title>
            <link>https://www.plugnmeet.org/blog/self-hosted-open-source-webinar-software</link>
            <guid>https://www.plugnmeet.org/blog/self-hosted-open-source-webinar-software</guid>
            <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In an era dominated by digital communication, webinars and online meetings have become indispensable. Yet, many organizations find themselves trapped in the rigid, often expensive, ecosystems of proprietary software like Zoom or GoToWebinar. These tools, while convenient, often come with hidden costs, limited customization, and significant concerns about data privacy and ownership.]]></description>
            <content:encoded><![CDATA[<p>In an era dominated by digital communication, webinars and online meetings have become indispensable. Yet, many organizations find themselves trapped in the rigid, often expensive, ecosystems of proprietary software like Zoom or GoToWebinar. These tools, while convenient, often come with hidden costs, limited customization, and significant concerns about data privacy and ownership.</p>
<p>What if you could host professional webinars and meetings with unparalleled control, complete data privacy, and the flexibility to tailor every aspect to your brand and workflow? This is the promise of <strong>open source webinar software</strong> and <strong>self-hosted meeting</strong> platforms. At plugNmeet, we believe in empowering you to reclaim ownership of your digital interactions.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="the-power-of-open-source-webinar-software-more-than-just-a-tool">The Power of Open Source Webinar Software: More Than Just a Tool<a href="https://www.plugnmeet.org/blog/self-hosted-open-source-webinar-software#the-power-of-open-source-webinar-software-more-than-just-a-tool" class="hash-link" aria-label="Direct link to The Power of Open Source Webinar Software: More Than Just a Tool" title="Direct link to The Power of Open Source Webinar Software: More Than Just a Tool" translate="no">​</a></h2>
<p>When you search for "open source webinar software," you're looking for freedom. You want to own your brand, manage your audience, and create a unique, professional presentation without being locked into a vendor's ecosystem.</p>
<p>Proprietary webinar tools often treat a webinar like a glorified meeting, lacking the specific features needed for a structured, engaging event. An open-source platform, however, allows for:</p>
<ol>
<li class=""><strong>Clear Roles &amp; Moderation:</strong> Distinguish between presenters and attendees, with granular control over permissions. A dedicated admin can manage the audience, while the presenter focuses on content delivery, as detailed in our post on <a class="" href="https://www.plugnmeet.org/blog/open-source-webinar-software-platform">open source webinar software platforms</a>.</li>
<li class=""><strong>Structured Engagement:</strong> Move beyond chaotic chat. Implement live polls for Q&amp;A, quizzes, and use features like "Raise Hand" for organized interaction.</li>
<li class=""><strong>Complete Branding:</strong> Your webinar is your show. Open-source solutions, especially those built with an API-first approach like plugNmeet, allow for deep customization – from logos and colors to entirely rebuilding the layout to match your brand identity perfectly.</li>
<li class=""><strong>Content Ownership:</strong> Recordings are valuable assets. With open-source, you own your content, saving it to your servers and automating post-webinar workflows (e.g., uploading to YouTube, sending to attendees).</li>
</ol>
<p>Open source isn't just about cost; it's about community, transparency, and the ability to adapt the software to your exact needs, rather than adapting your needs to the software.</p>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="why-self-hosted-meeting-solutions-are-non-negotiable-for-control--privacy">Why Self-Hosted Meeting Solutions are Non-Negotiable for Control &amp; Privacy<a href="https://www.plugnmeet.org/blog/self-hosted-open-source-webinar-software#why-self-hosted-meeting-solutions-are-non-negotiable-for-control--privacy" class="hash-link" aria-label="Direct link to Why Self-Hosted Meeting Solutions are Non-Negotiable for Control &amp; Privacy" title="Direct link to Why Self-Hosted Meeting Solutions are Non-Negotiable for Control &amp; Privacy" translate="no">​</a></h2>
<p>The "self-hosted meeting" aspect takes control to the next level. When you host your own video conferencing infrastructure, you gain benefits that proprietary cloud services simply cannot offer:</p>
<ul>
<li class=""><strong>Unmatched Data Ownership &amp; Privacy:</strong> Your conversations, recordings, and user data reside entirely within your infrastructure. This is critical for compliance with regulations like GDPR, HIPAA, and other industry-specific privacy standards. You are in command of your data, not a third-party vendor.</li>
<li class=""><strong>Cost-Effectiveness at Scale:</strong> Proprietary solutions often charge per-user or per-minute, leading to escalating costs. Self-hosting offers predictable infrastructure costs, making it a significantly more cost-effective solution for long-term, large-scale deployments. For strategies on <a class="" href="https://www.plugnmeet.org/blog/hosting-large-scale-events-the-smart-way">hosting large-scale events</a>, we recommend a broadcast studio model to maximize scalability and minimize costs.</li>
<li class=""><strong>Tailored Performance &amp; Reliability:</strong> By hosting on your own servers, you can optimize network and resources specifically for your users. This ensures a smoother, more reliable experience, especially when combined with smart streaming techniques like Simulcast and Dynacast, which adapt video quality to individual network conditions and pause off-screen video streams to save bandwidth, as explained in our article on <a class="" href="https://www.plugnmeet.org/blog/smart-video-streaming">smart video streaming</a>.</li>
<li class=""><strong>Deep Customization &amp; Integration:</strong> Beyond just branding, self-hosting allows for profound customization. You can integrate video conferencing directly into your existing applications and workflows, using powerful APIs to define feature sets for each room, embed web content, and even connect with traditional telephony via <a class="" href="https://www.plugnmeet.org/blog/sip-dial-in-for-video-conferencing">SIP Dial-In</a> for participants without internet access.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="plugnmeet-your-foundation-for-open-source-self-hosted-excellence">plugNmeet: Your Foundation for Open Source, Self-Hosted Excellence<a href="https://www.plugnmeet.org/blog/self-hosted-open-source-webinar-software#plugnmeet-your-foundation-for-open-source-self-hosted-excellence" class="hash-link" aria-label="Direct link to plugNmeet: Your Foundation for Open Source, Self-Hosted Excellence" title="Direct link to plugNmeet: Your Foundation for Open Source, Self-Hosted Excellence" translate="no">​</a></h2>
<p>plugNmeet is built from the ground up to deliver on these promises. As an open-source, WebRTC-based platform, it provides the flexible, powerful, and secure foundation you need to build a professional, branded, and automated webinar and meeting experience that is truly your own.</p>
<p>Key features that highlight our commitment to open source and self-hosting include:</p>
<ul>
<li class=""><strong>Adaptive Streaming (Simulcast &amp; Dynacast):</strong> Ensures smooth, uninterrupted calls even on challenging networks by intelligently managing video streams.</li>
<li class=""><strong>RTMP &amp; WHIP Ingress:</strong> Stream like a pro by bringing professional tools like OBS Studio directly into your meeting room, appearing as a participant, as detailed in our guide on <a class="" href="https://www.plugnmeet.org/blog/obs-rtmp-whip-ingress">OBS RTMP WHIP Ingress</a>.</li>
<li class=""><strong>SIP Dial-In:</strong> Bridge the gap for participants without internet access, allowing them to join audio via any standard telephone.</li>
<li class=""><strong>AI Meeting Agent:</strong> Enhance productivity with live spoken translations, automated summaries, and full transcriptions.</li>
<li class=""><strong>End-to-End Encryption (E2EE):</strong> For the <a class="" href="https://www.plugnmeet.org/docs/security-overview">highest level of privacy</a>, ensuring your conversations remain completely confidential.</li>
<li class=""><strong>Comprehensive APIs &amp; SDKs:</strong> For developers to build custom solutions and integrate deeply with existing systems.</li>
<li class=""><strong>CMS Plugins:</strong> Ready-to-use plugins for WordPress, Joomla, and Moodle for easy integration without coding. For example, you can <a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-service-with-wordpress">start your Webinar meeting with WordPress</a> in minutes.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="take-control-of-your-communication-future">Take Control of Your Communication Future<a href="https://www.plugnmeet.org/blog/self-hosted-open-source-webinar-software#take-control-of-your-communication-future" class="hash-link" aria-label="Direct link to Take Control of Your Communication Future" title="Direct link to Take Control of Your Communication Future" translate="no">​</a></h2>
<p>In a world where digital interactions are paramount, settling for generic, proprietary solutions means sacrificing control, privacy, and the ability to truly brand and tailor your user experience.</p>
<p>Choose an <strong>open source webinar software</strong> and <strong>self-hosted meeting</strong> platform like plugNmeet to empower your organization with a secure, scalable, and fully customized video conferencing solution. Reclaim ownership of your communication and deliver an unparalleled experience to your users.</p>
<hr>
<p><strong>Ready to build your perfect webinar and meeting platform?</strong></p>
<ul>
<li class=""><strong>Explore our <a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Live Demo</a> to see the features in action.</strong></li>
<li class=""><strong>Visit our <a href="https://github.com/mynaparrot/plugNmeet-server" target="_blank" rel="noopener noreferrer" class="">GitHub Repository</a> to get started with self-hosting.</strong></li>
<li class=""><strong>Dive into our <a class="" href="https://www.plugnmeet.org/docs/api/intro">API Documentation</a> to begin building your custom solution.</strong></li>
</ul>]]></content:encoded>
            <category>webinar</category>
            <category>open-source</category>
            <category>self-hosted</category>
            <category>video-conferencing</category>
            <category>platform</category>
            <category>privacy</category>
            <category>customization</category>
            <category>control</category>
            <category>scalability</category>
            <category>events</category>
        </item>
        <item>
            <title><![CDATA[Why Self-Hosted, Customizable Video Conferencing with plugNmeet Outperforms Proprietary Solutions]]></title>
            <link>https://www.plugnmeet.org/blog/self-hosted-customizable-video-conferencing</link>
            <guid>https://www.plugnmeet.org/blog/self-hosted-customizable-video-conferencing</guid>
            <pubDate>Mon, 30 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In today's interconnected world, video conferencing has become indispensable. But as businesses, educators, and healthcare professionals rely more heavily on these tools, a critical question arises: who truly controls your communication? Proprietary solutions often come with hidden costs, limited flexibility, and significant privacy concerns.]]></description>
            <content:encoded><![CDATA[<p>In today's interconnected world, video conferencing has become indispensable. But as businesses, educators, and healthcare professionals rely more heavily on these tools, a critical question arises: <strong>who truly controls your communication?</strong> Proprietary solutions often come with hidden costs, limited flexibility, and significant privacy concerns.</p>
<p>Enter plugNmeet: an open-source, self-hosted WebRTC platform designed to give you ultimate control, unparalleled customization, and robust security, fundamentally outperforming generic, off-the-shelf alternatives.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="the-power-of-self-hosting-unmatched-control--privacy">The Power of Self-Hosting: Unmatched Control &amp; Privacy<a href="https://www.plugnmeet.org/blog/self-hosted-customizable-video-conferencing#the-power-of-self-hosting-unmatched-control--privacy" class="hash-link" aria-label="Direct link to The Power of Self-Hosting: Unmatched Control &amp; Privacy" title="Direct link to The Power of Self-Hosting: Unmatched Control &amp; Privacy" translate="no">​</a></h2>
<p>When you choose a proprietary video conferencing service, your data resides on their servers, subject to their policies, security practices, and potential data mining. Self-hosting with plugNmeet flips this script, putting you firmly in command.</p>
<ul>
<li class=""><strong>Data Ownership &amp; Privacy:</strong> Your conversations, recordings, and user data remain entirely within your infrastructure. This is crucial for compliance with regulations like HIPAA, GDPR, and other industry-specific privacy standards. With plugNmeet, you own your data, ensuring maximum privacy and peace of mind. For a deeper dive into our security architecture, refer to our <a class="" href="https://www.plugnmeet.org/docs/security-overview">Security Overview</a>.</li>
<li class=""><strong>Cost-Effectiveness at Scale:</strong> Proprietary solutions often charge per-user or per-minute, leading to escalating costs as your needs grow. Self-hosting offers predictable infrastructure costs, making it a significantly more cost-effective solution for long-term, large-scale deployments.</li>
<li class=""><strong>Reliability &amp; Performance:</strong> By hosting on your own servers, you can optimize your network and resources specifically for your users, ensuring a smoother, more reliable experience tailored to your audience's geographical location and bandwidth.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="beyond-branding-true-customization-for-your-unique-needs">Beyond Branding: True Customization for Your Unique Needs<a href="https://www.plugnmeet.org/blog/self-hosted-customizable-video-conferencing#beyond-branding-true-customization-for-your-unique-needs" class="hash-link" aria-label="Direct link to Beyond Branding: True Customization for Your Unique Needs" title="Direct link to Beyond Branding: True Customization for Your Unique Needs" translate="no">​</a></h2>
<p>Many "white-label" solutions merely allow you to swap a logo. plugNmeet offers a level of customization that transforms the platform into an integral part of your ecosystem, not just an embedded tool.</p>
<ul>
<li class=""><strong>Pixel-Perfect Branding:</strong> From custom URLs and logos to colors and layouts, plugNmeet allows you to tailor every visual aspect to match your brand identity seamlessly. For more on this, see our post on <a class="" href="https://www.plugnmeet.org/blog/true-white-label-video-conferencing">True White-Label Video Conferencing</a>. Our <code>getClientFiles</code> API even allows you to inject the client directly into your application for a truly native feel, as detailed in our <a class="" href="https://www.plugnmeet.org/docs/developer-guide/design-customisation">Design Customization Guide</a> and <a class="" href="https://www.plugnmeet.org/docs/api/get-client-files">getClientFiles API Documentation</a>.</li>
<li class=""><strong>Feature Control via API:</strong> Our powerful server-side API lets you define the exact feature set for each room. Need a minimalist one-to-one chat? Disable the whiteboard and polls. Building a virtual classroom? Enable breakout rooms, shared notepads, and advanced moderation tools. This granular control ensures your users only see what's relevant, creating a streamlined and focused experience.</li>
<li class=""><strong>Tailored Workflows:</strong> Whether you're building a telehealth platform, an online learning environment, or a secure corporate communication hub, plugNmeet's flexibility allows you to integrate video conferencing directly into your existing workflows and applications.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="feature-spotlight-what-makes-plugnmeet-stand-out">Feature Spotlight: What Makes plugNmeet Stand Out<a href="https://www.plugnmeet.org/blog/self-hosted-customizable-video-conferencing#feature-spotlight-what-makes-plugnmeet-stand-out" class="hash-link" aria-label="Direct link to Feature Spotlight: What Makes plugNmeet Stand Out" title="Direct link to Feature Spotlight: What Makes plugNmeet Stand Out" translate="no">​</a></h2>
<p>While control and customization are paramount, plugNmeet also boasts a rich set of features that rival, and often surpass, proprietary offerings.</p>
<ul>
<li class=""><strong>AI Meeting Agent:</strong> Transform your meetings into actionable insights with live spoken translations, automated summaries, and full transcriptions. This intelligent agent enhances productivity and accessibility. Learn more about <a class="" href="https://www.plugnmeet.org/blog/how-to-add-ai-meeting-assistant-features">How to Add AI Features</a>.</li>
<li class=""><strong>End-to-End Encryption (E2EE):</strong> Beyond standard WebRTC security, plugNmeet adds an additional layer of true E2EE, ensuring that even the server cannot access the content of your private conversations. This is privacy by design. Explore our <a class="" href="https://www.plugnmeet.org/blog/e2ee-key-models-guide">E2EE Key Models Guide</a> and <a class="" href="https://www.plugnmeet.org/blog/how-to-enable-end-to-end-encryption">How to Enable End-to-End Encryption</a>.</li>
<li class=""><strong>Advanced Collaboration Tools:</strong> Boost productivity with interactive whiteboards, collaborative notepads, screen sharing, and breakout rooms for focused group discussions.</li>
<li class=""><strong>Scalability &amp; Performance:</strong> Built with modern technologies like Go, LiveKit, and NATS, plugNmeet is engineered for horizontal scaling, supporting hundreds of participants with optimal performance, even on challenging networks thanks to adaptive streaming. Discover how our <a class="" href="https://www.plugnmeet.org/blog/scaling-architecture-saves-money">Scaling Architecture Saves Money</a> and dive into our <a class="" href="https://www.plugnmeet.org/blog/backend-architecture-deep-dive">Backend Architecture Deep Dive</a>.</li>
<li class=""><strong>Recording &amp; Broadcasting:</strong> Capture sessions in HD as portable MP4 files to your own server, or stream live to platforms like YouTube and Facebook using built-in RTMP/WHIP support. You own your content, always. Read our <a class="" href="https://www.plugnmeet.org/blog/guide-to-managing-meeting-recordings">Guide to Managing Meeting Recordings</a> and learn about <a class="" href="https://www.plugnmeet.org/blog/smart-scaling-ha-recorder">Smart Scaling HA Recorder</a>.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="seamless-integration--migration">Seamless Integration &amp; Migration<a href="https://www.plugnmeet.org/blog/self-hosted-customizable-video-conferencing#seamless-integration--migration" class="hash-link" aria-label="Direct link to Seamless Integration &amp; Migration" title="Direct link to Seamless Integration &amp; Migration" translate="no">​</a></h2>
<p>Worried about switching from an existing platform? plugNmeet makes it easy.</p>
<ul>
<li class=""><strong>Easy Migration:</strong> For users looking to move away from platforms like BigBlueButton, plugNmeet offers a modern, performant, and highly customizable alternative. Our architecture simplifies the transition, allowing you to leverage your existing knowledge while gaining superior control and features. See our guide on <a class="" href="https://www.plugnmeet.org/docs/tutorials/migration-from-bbb">Migration from BigBlueButton</a>.</li>
<li class=""><strong>CMS Plugins:</strong> Integrate plugNmeet effortlessly into popular content management systems like WordPress, Joomla, and Moodle with our official plugins, requiring no coding. Find out more in our guides for <a class="" href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide">WordPress Video Conferencing</a>, <a class="" href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide">Joomla Video Conferencing</a>, and <a class="" href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide">Moodle Video Conferencing</a>.</li>
<li class=""><strong>Developer-Friendly SDKs:</strong> For custom applications, our comprehensive <a class="" href="https://www.plugnmeet.org/docs/api/intro">API Documentation</a> and SDKs (PHP, JavaScript, Python) provide the tools developers need for deep integration.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-choose-control-choose-customization-choose-plugnmeet">Conclusion: Choose Control, Choose Customization, Choose plugNmeet<a href="https://www.plugnmeet.org/blog/self-hosted-customizable-video-conferencing#conclusion-choose-control-choose-customization-choose-plugnmeet" class="hash-link" aria-label="Direct link to Conclusion: Choose Control, Choose Customization, Choose plugNmeet" title="Direct link to Conclusion: Choose Control, Choose Customization, Choose plugNmeet" translate="no">​</a></h2>
<p>In a world increasingly reliant on digital communication, settling for generic, proprietary video conferencing solutions means sacrificing control, privacy, and the ability to truly brand and tailor your user experience.</p>
<p>plugNmeet offers a powerful, open-source alternative that empowers you to build a secure, scalable, and fully customized video conferencing platform that is truly your own. Reclaim ownership of your communication and deliver an unparalleled experience to your users.</p>
<p><strong>Ready to take control of your video conferencing?</strong></p>
<ul>
<li class=""><strong>Explore our <a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Live Demo</a> to see the features in action.</strong></li>
<li class=""><strong>Visit our <a href="https://github.com/mynaparrot/plugNmeet-server" target="_blank" rel="noopener noreferrer" class="">GitHub Repository</a> to get started with self-hosting.</strong></li>
<li class=""><strong>Dive into our <a class="" href="https://www.plugnmeet.org/docs/api/intro">API Documentation</a> to begin building your custom solution.</strong></li>
</ul>]]></content:encoded>
            <category>self-hosted</category>
            <category>open-source</category>
            <category>customization</category>
            <category>white-label</category>
            <category>security</category>
            <category>webrtc</category>
            <category>zoom-alternative</category>
            <category>video-conferencing</category>
            <category>platform</category>
        </item>
        <item>
            <title><![CDATA[How to Reduce Video Conferencing Costs by up to 80% with Open-Source Software]]></title>
            <link>https://www.plugnmeet.org/blog/how-to-reduce-video-conferencing-costs-with-open-source</link>
            <guid>https://www.plugnmeet.org/blog/how-to-reduce-video-conferencing-costs-with-open-source</guid>
            <pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Your organization relies on video conferencing. It's essential for team meetings, sales demos, and customer support. But as your company grows, your monthly bill from services like Zoom, Google Meet, or Microsoft Teams is growing with it. That predictable per-user, per-month fee is starting to look less like a convenience and more like a major operational expense.]]></description>
            <content:encoded><![CDATA[<p>Your organization relies on video conferencing. It's essential for team meetings, sales demos, and customer support. But as your company grows, your monthly bill from services like Zoom, Google Meet, or Microsoft Teams is growing with it. That predictable per-user, per-month fee is starting to look less like a convenience and more like a major operational expense.</p>
<p>What if you could cut that bill by up to 80% while gaining more control and better branding?</p>
<p>It's not a fantasy. By switching from a proprietary SaaS "rental" model to a self-hosted "ownership" model with an open-source platform like <strong>Plug-N-Meet</strong>, you can dramatically reduce your Total Cost of Ownership (TCO). This article will break down the numbers and show you how.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-saas-rental-model-a-penalty-on-growth">The SaaS "Rental" Model: A Penalty on Growth<a href="https://www.plugnmeet.org/blog/how-to-reduce-video-conferencing-costs-with-open-source#the-saas-rental-model-a-penalty-on-growth" class="hash-link" aria-label="Direct link to The SaaS &quot;Rental&quot; Model: A Penalty on Growth" title="Direct link to The SaaS &quot;Rental&quot; Model: A Penalty on Growth" translate="no">​</a></h3>
<p>The pricing model for most SaaS video platforms is simple and seductive: a flat fee per host, per month. It's easy to budget for when you have 10 employees. But what happens when you have 100?</p>
<p>Let's look at a common scenario.</p>
<p><strong>Scenario: A Company with 50 Hosts</strong></p>
<ul>
<li class=""><strong>SaaS Provider Cost (e.g., Zoom Pro):</strong>
<ul>
<li class="">Price per host: ~$15.99 / month</li>
<li class="">Total monthly cost: 50 hosts * $15.99 = <strong>$799.50 / month</strong></li>
<li class="">Total annual cost: <strong>$9,594 / year</strong></li>
</ul>
</li>
</ul>
<p>This is a significant, recurring operational expense (OpEx). And it gets worse. This model penalizes you for success. If your company grows to 100 hosts, your bill doubles to nearly <strong>$20,000 per year</strong>. You are paying more for the exact same service, simply because your team is bigger.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-self-hosted-ownership-model-predictable-and-scalable">The Self-Hosted "Ownership" Model: Predictable and Scalable<a href="https://www.plugnmeet.org/blog/how-to-reduce-video-conferencing-costs-with-open-source#the-self-hosted-ownership-model-predictable-and-scalable" class="hash-link" aria-label="Direct link to The Self-Hosted &quot;Ownership&quot; Model: Predictable and Scalable" title="Direct link to The Self-Hosted &quot;Ownership&quot; Model: Predictable and Scalable" translate="no">​</a></h3>
<p>Now, let's compare this to a self-hosted model using the open-source Plug-N-Meet platform. With this model, you don't pay any license fees. Your only significant cost is the server hardware you run the software on.</p>
<p><strong>Scenario: The Same Company with 50 Hosts (or 100, or 200)</strong></p>
<ul>
<li class=""><strong>Self-Hosted Infrastructure Cost (e.g., Hetzner Cloud Server):</strong>
<ul>
<li class="">Recommended server: A powerful cloud instance like the Hetzner CCX33 (8 vCPUs, 32 GB RAM).</li>
<li class="">Total monthly cost: <strong>~€55 / month (approx. $60 / month)</strong></li>
<li class="">Total annual cost: <strong>~$720 / year</strong></li>
</ul>
</li>
</ul>
<p>Let's compare the annual costs:</p>
<ul>
<li class=""><strong>SaaS Model:</strong> <strong>$9,594</strong></li>
<li class=""><strong>Self-Hosted Model:</strong> <strong>$720</strong></li>
</ul>
<p>That's a cost reduction of <strong>over 92%</strong>.</p>
<p>Even if you choose a more expensive cloud provider or a larger server for higher capacity, the savings are dramatic. A comparable server on DigitalOcean or AWS might cost $150-$250 per month, which still represents a <strong>cost saving of over 80%</strong> compared to the SaaS alternative.</p>
<p>The best part? This cost is <strong>fixed</strong>. If your company grows from 50 to 100 hosts, your server cost doesn't change. You can accommodate more users for the same price, rewarding your growth instead of penalizing it.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="but-what-about-hidden-costs">But What About "Hidden" Costs?<a href="https://www.plugnmeet.org/blog/how-to-reduce-video-conferencing-costs-with-open-source#but-what-about-hidden-costs" class="hash-link" aria-label="Direct link to But What About &quot;Hidden&quot; Costs?" title="Direct link to But What About &quot;Hidden&quot; Costs?" translate="no">​</a></h3>
<p>Skeptics of self-hosting often point to hidden costs like maintenance and setup. Let's address those directly.</p>
<ul>
<li class=""><strong>Setup Complexity:</strong> This used to be a valid concern. Today, with automated tools, it's largely a solved problem. Plug-N-Meet provides a <strong><a class="" href="https://www.plugnmeet.org/docs/installation">one-line installation script</a></strong> that sets up a production-ready server in about 15 minutes. You don't need to be a systems administration expert.</li>
<li class=""><strong>Maintenance:</strong> The server requires minimal maintenance. Running a software update is as simple as executing another one-line script. For most organizations, this amounts to less than an hour of work per month.</li>
<li class=""><strong>Reliability:</strong> Modern cloud providers offer extremely high uptime (often 99.99%). Your self-hosted solution, running on reliable infrastructure, is just as dependable as a SaaS service, with the added benefit that you aren't affected by their widespread outages.</li>
</ul>
<p>When you weigh a few hours of optional maintenance per year against a potential saving of thousands of dollars, the financial case becomes overwhelmingly clear.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-stop-renting-start-owning">Conclusion: Stop Renting, Start Owning<a href="https://www.plugnmeet.org/blog/how-to-reduce-video-conferencing-costs-with-open-source#conclusion-stop-renting-start-owning" class="hash-link" aria-label="Direct link to Conclusion: Stop Renting, Start Owning" title="Direct link to Conclusion: Stop Renting, Start Owning" translate="no">​</a></h3>
<p>The per-user, per-month SaaS model is a brilliant business strategy—for the provider. For the customer, it's a treadmill of escalating costs that actively works against your growth.</p>
<p>By switching to a self-hosted, open-source platform like Plug-N-Meet, you move from being a tenant to an owner. You gain a predictable, low-cost infrastructure that scales with your success. You also get the invaluable strategic benefits of full brand control, deeper integration, and absolute data privacy.</p>
<p>If your organization is feeling the pain of rising SaaS subscription costs, it's time to run the numbers. The move to self-hosting isn't just a technical decision; it's one of the smartest financial decisions you can make.</p>
<hr>
<p><strong>Ready to slash your video conferencing costs?</strong></p>
<ul>
<li class=""><strong>See our detailed <a class="" href="https://www.plugnmeet.org/blog/scaling-architecture-saves-money">Server Recommendations</a>.</strong></li>
<li class=""><strong>Follow our simple <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a> to get started.</strong></li>
<li class=""><strong>Read more about the <a class="" href="https://www.plugnmeet.org/blog/own-vs-rent-video-platform">Business Case for Self-Hosting</a>.</strong></li>
</ul>]]></content:encoded>
            <category>business</category>
            <category>cost-saving</category>
            <category>tco</category>
            <category>open-source</category>
            <category>saas-alternative</category>
            <category>pricing</category>
            <category>self-hosting</category>
        </item>
        <item>
            <title><![CDATA[How to Secure Your Online Classroom: A Teacher's Guide to Privacy and Safety]]></title>
            <link>https://www.plugnmeet.org/blog/how-to-secure-your-online-classroom-for-teachers</link>
            <guid>https://www.plugnmeet.org/blog/how-to-secure-your-online-classroom-for-teachers</guid>
            <pubDate>Sat, 28 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[As an educator, your primary goal is to create a safe, respectful, and effective learning environment. In a physical classroom, you have established routines for this: you close the door to prevent interruptions, you set rules for speaking, and you manage the flow of the conversation.]]></description>
            <content:encoded><![CDATA[<p>As an educator, your primary goal is to create a safe, respectful, and effective learning environment. In a physical classroom, you have established routines for this: you close the door to prevent interruptions, you set rules for speaking, and you manage the flow of the conversation.</p>
<p>But how do you replicate this in a virtual classroom? An unsecured online meeting can quickly devolve into chaos, with uninvited guests, background noise, and constant interruptions.</p>
<p>This guide will walk you through the simple but powerful tools available in Plug-N-Meet that allow you to take control of your digital classroom. Think of these features as your new classroom management toolkit, designed to ensure your online sessions are as safe, private, and productive as your in-person ones.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="1-lock-the-door-the-waiting-room">1. Lock the Door: The Waiting Room<a href="https://www.plugnmeet.org/blog/how-to-secure-your-online-classroom-for-teachers#1-lock-the-door-the-waiting-room" class="hash-link" aria-label="Direct link to 1. Lock the Door: The Waiting Room" title="Direct link to 1. Lock the Door: The Waiting Room" translate="no">​</a></h3>
<p>You wouldn't let just anyone walk into your classroom off the street. The same principle should apply online. The <strong>Waiting Room</strong> is your digital lobby, giving you complete control over who enters your session.</p>
<ul>
<li class=""><strong>How it Works:</strong> When you enable the Waiting Room for your class, any student who clicks the join link is placed on a "waiting" screen. You, the teacher, will see a notification and a list of everyone waiting to get in. You can then admit them one by one or all at once.</li>
<li class=""><strong>Why it's Essential:</strong>
<ul>
<li class=""><strong>Prevents "Zoombombing":</strong> This is the single most effective way to stop uninvited guests from disrupting your class. If you don't recognize a name, you don't have to let them in.</li>
<li class=""><strong>Control the Start Time:</strong> It allows you to prepare your lesson materials and admit all students at the same time, ensuring everyone starts on the same page.</li>
</ul>
</li>
</ul>
<p><strong>How to Enable it:</strong> You can enable the Waiting Room in your room settings when you create the class in your LMS (like <strong><a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-moodle">Moodle</a></strong>) or via the API.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="2-set-the-rules-granular-lock-settings">2. Set the Rules: Granular Lock Settings<a href="https://www.plugnmeet.org/blog/how-to-secure-your-online-classroom-for-teachers#2-set-the-rules-granular-lock-settings" class="hash-link" aria-label="Direct link to 2. Set the Rules: Granular Lock Settings" title="Direct link to 2. Set the Rules: Granular Lock Settings" translate="no">​</a></h3>
<p>Once your students are in the room, you need to set the ground rules for participation. Plug-N-Meet's <strong><a class="" href="https://www.plugnmeet.org/blog/moderator-guide-room-lock-settings">Room Lock Settings</a></strong> panel is your command center for this.</p>
<ul>
<li class=""><strong>Lock Microphones (Your Most-Used Tool):</strong> This is the equivalent of asking the class to be quiet while you're speaking. It mutes all participants and prevents them from unmuting themselves. This is essential for lectures, presentations, or when a specific student is speaking.</li>
<li class=""><strong>Lock Webcams:</strong> In large classes, having dozens of webcams active can be distracting and consume a lot of bandwidth. Locking webcams helps focus attention on the speaker or the shared content.</li>
<li class=""><strong>Lock Public Chat:</strong> While chat is a great tool, it can sometimes become a source of distraction. You can temporarily lock the chat to bring focus back to the lesson, and then unlock it for a dedicated Q&amp;A session.</li>
<li class=""><strong>Lock Screen Sharing:</strong> Keep this locked by default to prevent students from accidentally taking over the presentation. You can unlock it when you want a student to share their work.</li>
</ul>
<p><strong>Pro Tip:</strong> Use these settings dynamically. Start your class with everything unlocked for a few minutes of social time, then lock microphones and chat when the formal lesson begins.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="3-the-gold-standard-of-privacy-end-to-end-encryption-e2ee">3. The Gold Standard of Privacy: End-to-End Encryption (E2EE)<a href="https://www.plugnmeet.org/blog/how-to-secure-your-online-classroom-for-teachers#3-the-gold-standard-of-privacy-end-to-end-encryption-e2ee" class="hash-link" aria-label="Direct link to 3. The Gold Standard of Privacy: End-to-End Encryption (E2EE)" title="Direct link to 3. The Gold Standard of Privacy: End-to-End Encryption (E2EE)" translate="no">​</a></h3>
<p>For sensitive conversations, such as a one-on-one meeting with a student or a parent, you may need the highest possible level of privacy. <strong>End-to-End Encryption (E2EE)</strong> provides a mathematical guarantee that no one—not even the server administrator—can access the audio or video of your conversation.</p>
<ul>
<li class=""><strong>How it Works:</strong> When E2EE is enabled, the video and audio are encrypted on your device and only decrypted on the recipient's device.</li>
<li class=""><strong>When to Use It:</strong> This is ideal for any situation requiring absolute confidentiality, such as discussions about grades, student well-being, or any other private matter.</li>
</ul>
<p><strong>How to Enable it:</strong> E2EE can be enabled on a per-room basis. Our guide on <strong><a class="" href="https://www.plugnmeet.org/blog/how-to-enable-end-to-end-encryption">How to Enable E2EE</a></strong> explains the simple steps. It's important to note that when you enable the highest level of E2EE, features that require the server to "see" the media, like cloud recording, are automatically disabled for your protection.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-you-are-in-control">Conclusion: You Are in Control<a href="https://www.plugnmeet.org/blog/how-to-secure-your-online-classroom-for-teachers#conclusion-you-are-in-control" class="hash-link" aria-label="Direct link to Conclusion: You Are in Control" title="Direct link to Conclusion: You Are in Control" translate="no">​</a></h3>
<p>A secure and productive online classroom doesn't happen by accident. It happens by design. By using these simple but powerful features, you can move from being a passive host to an active and effective classroom manager.</p>
<p>Start with the Waiting Room to control who enters, use the Lock Settings to manage the flow of conversation, and deploy E2EE for sensitive discussions. With these tools at your fingertips, you can create a virtual learning environment that is just as safe, private, and conducive to learning as your physical classroom.</p>
<hr>
<p><strong>Ready to take control of your virtual classroom?</strong></p>
<ul>
<li class=""><strong>Try these moderator features in our <a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Live Demo</a>.</strong></li>
<li class=""><strong>Read our full <a class="" href="https://www.plugnmeet.org/docs/user-guide/moderator">Moderator Guide</a> for more tips.</strong></li>
<li class=""><strong>Learn more about our commitment to <a class="" href="https://www.plugnmeet.org/blog/privacy-by-design-zero-trust">Privacy by Design</a>.</strong></li>
</ul>]]></content:encoded>
            <category>tutorial</category>
            <category>how-to</category>
            <category>security</category>
            <category>privacy</category>
            <category>education</category>
            <category>teacher</category>
            <category>moderator</category>
            <category>virtual-classroom</category>
        </item>
        <item>
            <title><![CDATA[Why Your Business Needs a White-Labeled Video Conferencing Solution]]></title>
            <link>https://www.plugnmeet.org/blog/why-your-business-needs-a-white-labeled-video-conferencing-solution</link>
            <guid>https://www.plugnmeet.org/blog/why-your-business-needs-a-white-labeled-video-conferencing-solution</guid>
            <pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Your brand is your most valuable asset. You've invested countless hours and resources into building a unique identity, a memorable customer experience, and a relationship of trust with your audience.]]></description>
            <content:encoded><![CDATA[<p>Your brand is your most valuable asset. You've invested countless hours and resources into building a unique identity, a memorable customer experience, and a relationship of trust with your audience.</p>
<p>So why, at the most critical moment of interaction—a sales demo, a customer support call, or a training session—do you send your customers to a third-party platform with someone else's logo on it?</p>
<p>Using a generic, off-the-shelf video conferencing tool is like a Michelin-starred restaurant serving its food in a McDonald's box. It breaks the experience, erodes brand value, and misses a huge opportunity. This is why forward-thinking businesses are moving away from generic SaaS tools and embracing <strong>white-labeled video conferencing solutions</strong> like Plug-N-Meet.</p>
<p>A white-label platform isn't just about changing the logo. It's about owning the entire customer journey and transforming a simple utility into a powerful strategic asset.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="1-it-builds-brand-trust-and-professionalism">1. It Builds Brand Trust and Professionalism<a href="https://www.plugnmeet.org/blog/why-your-business-needs-a-white-labeled-video-conferencing-solution#1-it-builds-brand-trust-and-professionalism" class="hash-link" aria-label="Direct link to 1. It Builds Brand Trust and Professionalism" title="Direct link to 1. It Builds Brand Trust and Professionalism" translate="no">​</a></h3>
<p>Imagine a new client is paying you for a premium consultation. You send them a meeting link. Which of these inspires more confidence?</p>
<ul>
<li class=""><code>your-company.zoom.us</code></li>
<li class=""><code>meet.your-company.com</code></li>
</ul>
<p>The answer is obvious. When the entire experience—from the URL to the logo in the corner to the brand colors of the interface—is yours, it sends a powerful message: you are professional, you are established, and you have invested in your own platform. It removes the jarring feeling of being handed off to another company and keeps the focus entirely on your brand.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="2-it-creates-a-seamless-frictionless-customer-experience">2. It Creates a Seamless, Frictionless Customer Experience<a href="https://www.plugnmeet.org/blog/why-your-business-needs-a-white-labeled-video-conferencing-solution#2-it-creates-a-seamless-frictionless-customer-experience" class="hash-link" aria-label="Direct link to 2. It Creates a Seamless, Frictionless Customer Experience" title="Direct link to 2. It Creates a Seamless, Frictionless Customer Experience" translate="no">​</a></h3>
<p>A white-labeled solution, especially one that can be deeply integrated, eliminates the "context switching" that plagues generic tools. Instead of your user having to open a separate app or a new tab that looks completely different from your website, the video experience feels like a natural extension of your own digital property.</p>
<p>With a platform like Plug-N-Meet, you can use the <strong><a class="" href="https://www.plugnmeet.org/blog/deep-integration-white-label-guide"><code>getClientFiles</code> API</a></strong> to embed the video client directly into your own application or customer portal. This means you can:</p>
<ul>
<li class="">Place the video call next to relevant customer data for your support agents.</li>
<li class="">Allow users to join a webinar directly from your community page without ever leaving your site.</li>
<li class="">Create a "single pane of glass" experience where the video is just one integrated component of a larger workflow.</li>
</ul>
<p>This seamlessness reduces friction, improves usability, and keeps your user engaged within your ecosystem.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="3-you-control-the-feature-set-and-user-journey">3. You Control the Feature Set and User Journey<a href="https://www.plugnmeet.org/blog/why-your-business-needs-a-white-labeled-video-conferencing-solution#3-you-control-the-feature-set-and-user-journey" class="hash-link" aria-label="Direct link to 3. You Control the Feature Set and User Journey" title="Direct link to 3. You Control the Feature Set and User Journey" translate="no">​</a></h3>
<p>Not all conversations are the same. A one-on-one sales call has very different needs than a 100-person training webinar.</p>
<p>Generic platforms offer a one-size-fits-all approach. A white-label, API-first platform gives you granular control to tailor the experience to the specific use case.</p>
<ul>
<li class=""><strong>For a simple support call:</strong> You can disable all features except video, audio, and chat to create a clean, minimalist interface.</li>
<li class=""><strong>For an interactive workshop:</strong> You can enable the whiteboard, breakout rooms, and polling to foster collaboration.</li>
<li class=""><strong>For a formal presentation:</strong> You can lock down all participant controls to ensure the focus remains on the speaker.</li>
</ul>
<p>This ability to programmatically define the user journey means you can deliver the perfect, purpose-built tool for every interaction, which is a level of service generic platforms can't match.</p>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="4-it-turns-a-cost-center-into-a-potential-revenue-stream">4. It Turns a Cost Center into a Potential Revenue Stream<a href="https://www.plugnmeet.org/blog/why-your-business-needs-a-white-labeled-video-conferencing-solution#4-it-turns-a-cost-center-into-a-potential-revenue-stream" class="hash-link" aria-label="Direct link to 4. It Turns a Cost Center into a Potential Revenue Stream" title="Direct link to 4. It Turns a Cost Center into a Potential Revenue Stream" translate="no">​</a></h3>
<p>When you pay a per-user subscription for a generic SaaS tool, video conferencing is a cost center that grows as your team grows.</p>
<p>When you own your platform, you flip the model. Because a self-hosted solution like Plug-N-Meet has no per-user fees, you can offer video conferencing services to your own customers. With our official plugins for <strong><a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-service-with-wordpress">WordPress</a></strong> and <strong><a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-service-with-joomla">Joomla</a></strong>, you can even use membership and e-commerce extensions to sell access to premium, branded meeting rooms—no coding required.</p>
<p>You transform a monthly expense into a revenue-generating product.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-stop-advertising-for-other-brands">Conclusion: Stop Advertising for Other Brands<a href="https://www.plugnmeet.org/blog/why-your-business-needs-a-white-labeled-video-conferencing-solution#conclusion-stop-advertising-for-other-brands" class="hash-link" aria-label="Direct link to Conclusion: Stop Advertising for Other Brands" title="Direct link to Conclusion: Stop Advertising for Other Brands" translate="no">​</a></h3>
<p>Every time a customer joins a meeting on a generic platform, you are paying to advertise for that platform.</p>
<p>A white-labeled video conferencing solution is an investment in your own brand. It enhances trust, creates a superior customer experience, and gives you the control to build a communication tool that perfectly fits your business. In a competitive market, owning this critical touchpoint is not a luxury; it's a strategic necessity.</p>
<hr>
<p><strong>Ready to put your brand front and center?</strong></p>
<ul>
<li class=""><strong>Learn more about our <a class="" href="https://www.plugnmeet.org/blog/deep-integration-white-label-guide">Deep Integration and White-Labeling Capabilities</a>.</strong></li>
<li class=""><strong>Try our <a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Live Demo</a> and imagine it with your own branding.</strong></li>
<li class=""><strong>Follow our <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a> to start building your own platform.</strong></li>
</ul>]]></content:encoded>
            <category>business</category>
            <category>branding</category>
            <category>white-label</category>
            <category>customer-experience</category>
            <category>marketing</category>
            <category>saas-alternative</category>
        </item>
        <item>
            <title><![CDATA[The Ultimate Guide to Joomla Video Conferencing: Integrate Live Meetings & Webinars]]></title>
            <link>https://www.plugnmeet.org/blog/joomla-video-conferencing-guide</link>
            <guid>https://www.plugnmeet.org/blog/joomla-video-conferencing-guide</guid>
            <pubDate>Wed, 18 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Joomla is a powerhouse for building robust, flexible websites, from community portals to sophisticated business sites. But when it's time to engage your audience with live video, you might find yourself sending them away from the beautiful site you've built to a generic, third-party conferencing tool. This breaks the user experience and undermines your brand.]]></description>
            <content:encoded><![CDATA[<p>Joomla is a powerhouse for building robust, flexible websites, from community portals to sophisticated business sites. But when it's time to engage your audience with live video, you might find yourself sending them away from the beautiful site you've built to a generic, third-party conferencing tool. This breaks the user experience and undermines your brand.</p>
<p>What if you could integrate a secure, fully-branded, and powerful video conferencing solution directly into your Joomla site, making it a native part of your user's journey?</p>
<p>With the official <strong>Plug-N-Meet component for Joomla</strong>, you can. This guide will show you how to transform your Joomla website into a dynamic communication platform, perfect for community meetings, webinars, online training, and even monetized services.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="why-a-native-joomla-integration-beats-external-tools">Why a Native Joomla Integration Beats External Tools<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#why-a-native-joomla-integration-beats-external-tools" class="hash-link" aria-label="Direct link to Why a Native Joomla Integration Beats External Tools" title="Direct link to Why a Native Joomla Integration Beats External Tools" translate="no">​</a></h3>
<p>Before we get into the setup, let's be clear about the strategic advantages of using a self-hosted, integrated solution like Plug-N-Meet instead of an external SaaS tool.</p>
<ol>
<li class=""><strong>A Seamless, Professional Brand Experience:</strong> Your users join meetings on your own domain, with your logo and brand colors. This reinforces professionalism and trust, keeping the user experience consistent with the rest of your site.</li>
<li class=""><strong>Full Control Over User Data:</strong> All user data and meeting recordings are stored on your own server, not in a third-party cloud. This is critical for maintaining data sovereignty and complying with privacy regulations like GDPR.</li>
<li class=""><strong>Cost-Effective Scaling:</strong> You escape the expensive per-user, per-month subscription fees that penalize growth. With a self-hosted model, your primary cost is a predictable server fee, allowing you to serve more users without your bill spiraling out of control.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-3-step-guide-to-your-first-joomla-meeting">The 3-Step Guide to Your First Joomla Meeting<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#the-3-step-guide-to-your-first-joomla-meeting" class="hash-link" aria-label="Direct link to The 3-Step Guide to Your First Joomla Meeting" title="Direct link to The 3-Step Guide to Your First Joomla Meeting" translate="no">​</a></h3>
<p>You can have a live video meeting room on your Joomla site in under 15 minutes.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-1-get-your-video-conferencing-engine">Step 1: Get Your Video Conferencing Engine<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#step-1-get-your-video-conferencing-engine" class="hash-link" aria-label="Direct link to Step 1: Get Your Video Conferencing Engine" title="Direct link to Step 1: Get Your Video Conferencing Engine" translate="no">​</a></h4>
<p>First, you need the server that will power your video calls. You can either self-host the open-source version with our <strong><a class="" href="https://www.plugnmeet.org/docs/installation">simple installation script</a></strong> or get an instant, managed server from <strong><a href="https://www.plugnmeet.cloud/" target="_blank" rel="noopener noreferrer" class="">plugnmeet Cloud</a></strong>. Both will provide you with an <strong>API Key</strong> and <strong>API Secret</strong>.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-2-install-the-free-plug-n-meet-component">Step 2: Install the Free Plug-N-Meet Component<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#step-2-install-the-free-plug-n-meet-component" class="hash-link" aria-label="Direct link to Step 2: Install the Free Plug-N-Meet Component" title="Direct link to Step 2: Install the Free Plug-N-Meet Component" translate="no">​</a></h4>
<p>From your Joomla administrator panel:</p>
<ol>
<li class="">Go to <strong>System &gt; Install &gt; Extensions</strong>.</li>
<li class="">Install the Plug-N-Meet component, which you can download from the official <a href="https://extensions.joomla.org/extension/plugnmeet/" target="_blank" rel="noopener noreferrer" class="">Joomla Extensions Directory</a>.</li>
<li class="">After installation, navigate to <strong>System &gt; Global Configuration</strong> and select <strong>plugNmeet</strong> from the components list.</li>
<li class="">Enter your <strong>API Key</strong> and <strong>API Secret</strong> and save the configuration.</li>
</ol>
<p>Your Joomla site is now fully connected to your video conferencing engine.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-3-create-and-display-your-room">Step 3: Create and Display Your Room<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#step-3-create-and-display-your-room" class="hash-link" aria-label="Direct link to Step 3: Create and Display Your Room" title="Direct link to Step 3: Create and Display Your Room" translate="no">​</a></h4>
<ol>
<li class="">Go to <strong>Components &gt; Plug N Meet &gt; Manage Rooms</strong> and click <strong>+ New</strong>.</li>
<li class="">Configure your room's features, such as its name, participant limits, and a welcome message.</li>
<li class="">To display the room, go to <strong>Menus &gt; Main Menu</strong> and click <strong>+ Add New Menu Item</strong>.</li>
<li class="">For the <strong>Menu Item Type</strong>, select <strong>Plug N Meet &gt; Single room</strong>.</li>
<li class="">In the <strong>Select a room</strong> dropdown, choose the room you just created and save the menu item.</li>
</ol>
<p>That's it. A new link will appear on your site's main menu, leading directly to your fully functional, branded video conferencing room.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="level-up-3-powerful-ways-to-use-video-on-your-joomla-site">Level Up: 3 Powerful Ways to Use Video on Your Joomla Site<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#level-up-3-powerful-ways-to-use-video-on-your-joomla-site" class="hash-link" aria-label="Direct link to Level Up: 3 Powerful Ways to Use Video on Your Joomla Site" title="Direct link to Level Up: 3 Powerful Ways to Use Video on Your Joomla Site" translate="no">​</a></h3>
<p>With the basics in place, you can now leverage Plug-N-Meet to build sophisticated video-based services.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-1-the-private-client-portal">Use Case 1: The Private Client Portal<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#use-case-1-the-private-client-portal" class="hash-link" aria-label="Direct link to Use Case 1: The Private Client Portal" title="Direct link to Use Case 1: The Private Client Portal" translate="no">​</a></h4>
<p>If you're a consultant, coach, or provide any one-on-one service, you can create private, secure meeting rooms for your clients.</p>
<ul>
<li class=""><strong>How to do it:</strong> Use Joomla's built-in Access Control Levels (ACL) to create a user group for each client. Create a dedicated menu item for their private meeting room and set its <strong>Access</strong> level to their specific user group. Now, only that logged-in client can see and access their private session link.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-2-the-community-webinar-platform">Use Case 2: The Community Webinar Platform<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#use-case-2-the-community-webinar-platform" class="hash-link" aria-label="Direct link to Use Case 2: The Community Webinar Platform" title="Direct link to Use Case 2: The Community Webinar Platform" translate="no">​</a></h4>
<p>Host live webinars and events directly on your site to engage your community.</p>
<ul>
<li class=""><strong>How to do it:</strong> Create a new room and configure it for a webinar. In the room settings, you can disable webcams for attendees, enable <strong>Cloud Recording</strong>, and activate features like <strong>Polling</strong> and <strong>Raise Hand</strong>. Set the menu item's access to "Public" or "Registered" and you have an instant webinar platform.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-3-the-monetized-membership-service">Use Case 3: The Monetized Membership Service<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#use-case-3-the-monetized-membership-service" class="hash-link" aria-label="Direct link to Use Case 3: The Monetized Membership Service" title="Direct link to Use Case 3: The Monetized Membership Service" translate="no">​</a></h4>
<p>This is where you can turn your Joomla site into a revenue-generating business. By combining Plug-N-Meet with a Joomla membership or e-commerce extension, you can sell access to premium video content. For a detailed walkthrough, see our guide on <strong><a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-service-with-joomla">How to Launch Your Own No-Code Video Conferencing Service with Joomla</a></strong>.</p>
<ul>
<li class=""><strong>How to do it:</strong>
<ol>
<li class="">Use your membership extension to create a "Premium Members" user group that paying subscribers are automatically added to.</li>
<li class="">Create a new menu item that links to your premium Plug-N-Meet room.</li>
<li class="">Set the <strong>Access</strong> level for this menu item to "Premium Members."</li>
</ol>
</li>
</ul>
<p>Now, only paying subscribers will be able to see the link and join your exclusive video sessions, creating an automated paywall.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-the-professional-video-solution-for-joomla">Conclusion: The Professional Video Solution for Joomla<a href="https://www.plugnmeet.org/blog/joomla-video-conferencing-guide#conclusion-the-professional-video-solution-for-joomla" class="hash-link" aria-label="Direct link to Conclusion: The Professional Video Solution for Joomla" title="Direct link to Conclusion: The Professional Video Solution for Joomla" translate="no">​</a></h3>
<p>Don't let external tools dictate your user experience. With the Plug-N-Meet component, you can seamlessly integrate a secure, scalable, and fully-branded video conferencing platform into the Joomla ecosystem.</p>
<p>It's a solution that's simple enough to get started in minutes but powerful enough to build a business on.</p>
<hr>
<p><strong>Ready to power up your Joomla site?</strong></p>
<ul>
<li class=""><strong><a href="https://extensions.joomla.org/extension/plugnmeet/" target="_blank" rel="noopener noreferrer" class="">Download the official Joomla Component</a></strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a> to see all the features in action.</strong></li>
<li class=""><strong>Follow our <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a> to get your server running.</strong></li>
</ul>]]></content:encoded>
            <category>joomla</category>
            <category>video-conferencing</category>
            <category>component</category>
            <category>no-code</category>
            <category>webinar</category>
            <category>membership</category>
            <category>joomla-video-conferencing</category>
        </item>
        <item>
            <title><![CDATA[The Ultimate Guide to Moodle Video Conferencing: From Virtual Classrooms to Collaborative Workshops]]></title>
            <link>https://www.plugnmeet.org/blog/moodle-video-conferencing-guide</link>
            <guid>https://www.plugnmeet.org/blog/moodle-video-conferencing-guide</guid>
            <pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[As an educator or administrator on Moodle, your goal is to create a seamless and engaging learning environment. But when it's time for a live virtual class, many are forced to compromise, sending students to an external Zoom or Teams link. This breaks the learning flow, creates a disjointed user experience, and raises valid concerns about student data privacy.]]></description>
            <content:encoded><![CDATA[<p>As an educator or administrator on Moodle, your goal is to create a seamless and engaging learning environment. But when it's time for a live virtual class, many are forced to compromise, sending students to an external Zoom or Teams link. This breaks the learning flow, creates a disjointed user experience, and raises valid concerns about student data privacy.</p>
<p>What if your virtual classroom was just another native Moodle activity, as easy to add as a quiz or a forum? What if it was a powerful, interactive space designed specifically for teaching, not just a generic meeting room?</p>
<p>With the official <strong>Plug-N-Meet plugin for Moodle</strong>, this is not just possible; it's simple. This guide will show you how to transform your Moodle site into a world-class, interactive e-learning platform.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="why-a-native-moodle-integration-is-a-game-changer">Why a Native Moodle Integration is a Game-Changer<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#why-a-native-moodle-integration-is-a-game-changer" class="hash-link" aria-label="Direct link to Why a Native Moodle Integration is a Game-Changer" title="Direct link to Why a Native Moodle Integration is a Game-Changer" translate="no">​</a></h3>
<p>Before we get to the "how," let's focus on the "why." Choosing a deeply integrated, self-hosted solution like Plug-N-Meet over a generic SaaS tool provides three critical advantages for any educational institution.</p>
<ol>
<li class=""><strong>A Seamless Student Experience:</strong> Students never leave your Moodle site. They join the live class directly from their course page, with the same branding and interface they already know. This reduces confusion and keeps them focused on the learning, not the technology.</li>
<li class=""><strong>Absolute Control Over Student Data:</strong> In education, privacy is paramount. With a self-hosted platform, all student data and class recordings are stored on your own servers, not on a third-party cloud. This gives you full control to ensure compliance with privacy regulations like GDPR and FERPA.</li>
<li class=""><strong>Predictable, Scalable Costs:</strong> Instead of paying a costly per-host, per-month subscription that penalizes you for adding more teachers, you have a fixed, predictable cost for your server infrastructure. This is a far more sustainable and cost-effective model for schools and universities.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-3-step-guide-to-your-first-moodle-virtual-classroom">The 3-Step Guide to Your First Moodle Virtual Classroom<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#the-3-step-guide-to-your-first-moodle-virtual-classroom" class="hash-link" aria-label="Direct link to The 3-Step Guide to Your First Moodle Virtual Classroom" title="Direct link to The 3-Step Guide to Your First Moodle Virtual Classroom" translate="no">​</a></h3>
<p>Getting started is incredibly fast. You can have your first meeting room live in under 15 minutes. For a more detailed walkthrough, you can also read our popular guide on <strong><a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-moodle">How to Add No-Code Video Conferencing to Your Moodle Courses</a></strong>.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-1-get-your-video-conferencing-engine">Step 1: Get Your Video Conferencing Engine<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#step-1-get-your-video-conferencing-engine" class="hash-link" aria-label="Direct link to Step 1: Get Your Video Conferencing Engine" title="Direct link to Step 1: Get Your Video Conferencing Engine" translate="no">​</a></h4>
<p>First, you need the server that will power your video calls. You can either self-host the open-source version with our <strong><a class="" href="https://www.plugnmeet.org/docs/installation">simple installation script</a></strong> or get an instant, managed server from <strong><a href="https://www.plugnmeet.cloud/" target="_blank" rel="noopener noreferrer" class="">plugnmeet Cloud</a></strong>. Both will provide you with an <strong>API Key</strong> and <strong>API Secret</strong>.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-2-install-the-free-plug-n-meet-plugin">Step 2: Install the Free Plug-N-Meet Plugin<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#step-2-install-the-free-plug-n-meet-plugin" class="hash-link" aria-label="Direct link to Step 2: Install the Free Plug-N-Meet Plugin" title="Direct link to Step 2: Install the Free Plug-N-Meet Plugin" translate="no">​</a></h4>
<p>From your Moodle Site Administration panel:</p>
<ol>
<li class="">Go to <strong>Site administration &gt; Plugins &gt; Install plugins</strong>.</li>
<li class="">Install the Plug-N-Meet plugin, which you can download from the official <a href="https://moodle.org/plugins/mod_plugnmeet" target="_blank" rel="noopener noreferrer" class="">Moodle plugins directory</a>.</li>
<li class="">After installation, navigate to the plugin settings page (<strong>Site administration &gt; Plugins &gt; Activity modules &gt; PlugNmeet</strong>).</li>
<li class="">Enter your <strong>API Key</strong> and <strong>API Secret</strong> and save the changes.</li>
</ol>
<p>Your Moodle site is now fully integrated with your video conferencing engine.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-3-add-the-plugnmeet-activity-to-your-course">Step 3: Add the "PlugNmeet" Activity to Your Course<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#step-3-add-the-plugnmeet-activity-to-your-course" class="hash-link" aria-label="Direct link to Step 3: Add the &quot;PlugNmeet&quot; Activity to Your Course" title="Direct link to Step 3: Add the &quot;PlugNmeet&quot; Activity to Your Course" translate="no">​</a></h4>
<ol>
<li class="">Navigate to the Moodle course where you want to add the live class.</li>
<li class="">Turn <strong>Edit mode</strong> on.</li>
<li class="">Click <strong>Add an activity or resource</strong> and select <strong>PlugNmeet</strong> from the list.</li>
<li class="">Configure your virtual classroom, giving it a name, a welcome message, and setting the schedule.</li>
</ol>
<p>That's it. Your students can now join a powerful, interactive virtual classroom directly from their Moodle course page.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="level-up-3-powerful-ways-to-use-video-in-moodle">Level Up: 3 Powerful Ways to Use Video in Moodle<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#level-up-3-powerful-ways-to-use-video-in-moodle" class="hash-link" aria-label="Direct link to Level Up: 3 Powerful Ways to Use Video in Moodle" title="Direct link to Level Up: 3 Powerful Ways to Use Video in Moodle" translate="no">​</a></h3>
<p>Plug-N-Meet is more than just a lecture tool. It's a suite of features designed for modern pedagogy.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-1-the-interactive-virtual-classroom">Use Case 1: The Interactive Virtual Classroom<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#use-case-1-the-interactive-virtual-classroom" class="hash-link" aria-label="Direct link to Use Case 1: The Interactive Virtual Classroom" title="Direct link to Use Case 1: The Interactive Virtual Classroom" translate="no">​</a></h4>
<p>Transform a passive lecture into an engaging, two-way experience.</p>
<ul>
<li class=""><strong>How to do it:</strong> Use the <strong><a class="" href="https://www.plugnmeet.org/blog/integrated-whiteboard-experience">Interactive Whiteboard</a></strong> to upload your presentation slides and annotate them in real-time. Use the <strong>Live Polling</strong> feature to quickly check for student understanding or run a quick quiz. All class sessions can be recorded to the cloud and automatically made available back in the Moodle course for students who missed the live session.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-2-the-collaborative-workshop-with-breakout-rooms">Use Case 2: The Collaborative Workshop with Breakout Rooms<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#use-case-2-the-collaborative-workshop-with-breakout-rooms" class="hash-link" aria-label="Direct link to Use Case 2: The Collaborative Workshop with Breakout Rooms" title="Direct link to Use Case 2: The Collaborative Workshop with Breakout Rooms" translate="no">​</a></h4>
<p>For project-based learning and group work, the <strong><a class="" href="https://www.plugnmeet.org/blog/mastering-breakout-rooms">Breakout Rooms</a></strong> feature is essential.</p>
<ul>
<li class=""><strong>How to do it:</strong> From within the live session, the teacher can split the students into smaller groups. Each breakout room gets its own private, fully-functional whiteboard and shared notepad, allowing for true small-group collaboration. The teacher can broadcast messages to all rooms or drop in to a specific group to check on their progress.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-3-the-secure-one-on-one-or-parent-teacher-conference">Use Case 3: The Secure One-on-One or Parent-Teacher Conference<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#use-case-3-the-secure-one-on-one-or-parent-teacher-conference" class="hash-link" aria-label="Direct link to Use Case 3: The Secure One-on-One or Parent-Teacher Conference" title="Direct link to Use Case 3: The Secure One-on-One or Parent-Teacher Conference" translate="no">​</a></h4>
<p>For private conversations, you need a higher level of security.</p>
<ul>
<li class=""><strong>How to do it:</strong> Create a PlugNmeet activity and, in the room settings, enable <strong><a class="" href="https://www.plugnmeet.org/blog/privacy-by-design-zero-trust">End-to-End Encryption (E2EE)</a></strong>. This provides a mathematical guarantee that the conversation is completely private and that not even the server administrator can access the audio or video. This is ideal for one-on-one student support, counseling sessions, or parent-teacher conferences where confidentiality is critical.</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-the-virtual-classroom-moodle-deserves">Conclusion: The Virtual Classroom Moodle Deserves<a href="https://www.plugnmeet.org/blog/moodle-video-conferencing-guide#conclusion-the-virtual-classroom-moodle-deserves" class="hash-link" aria-label="Direct link to Conclusion: The Virtual Classroom Moodle Deserves" title="Direct link to Conclusion: The Virtual Classroom Moodle Deserves" translate="no">​</a></h3>
<p>Stop compromising with external tools that break the learning experience. By integrating Plug-N-Meet, you can offer your teachers and students a powerful, secure, and seamlessly integrated virtual classroom that lives right where it belongs: inside Moodle.</p>
<p>It's a solution that's simple enough for any teacher to use but powerful enough to support the most innovative online teaching methods.</p>
<hr>
<p><strong>Ready to enhance your Moodle courses?</strong></p>
<ul>
<li class=""><strong><a href="https://moodle.org/plugins/mod_plugnmeet" target="_blank" rel="noopener noreferrer" class="">Download the official Moodle Plugin</a></strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a> to explore the teaching tools.</strong></li>
<li class=""><strong>Follow our <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a> to get your server running.</strong></li>
</ul>]]></content:encoded>
            <category>moodle</category>
            <category>video-conferencing</category>
            <category>plugin</category>
            <category>no-code</category>
            <category>virtual-classroom</category>
            <category>e-learning</category>
            <category>moodle-plugin</category>
            <category>moodle-video-conferencing</category>
        </item>
        <item>
            <title><![CDATA[The Ultimate Guide to WordPress Video Conferencing]]></title>
            <link>https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide</link>
            <guid>https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide</guid>
            <pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[WordPress has empowered millions to build anything from a simple blog to a complex e-commerce empire. But when it comes to one of the most critical forms of modern communication—live video—many site owners hit a wall. You're often left juggling clunky Zoom links, sending your users to third-party platforms, and sacrificing the professional, branded experience your website was designed to provide.]]></description>
            <content:encoded><![CDATA[<p>WordPress has empowered millions to build anything from a simple blog to a complex e-commerce empire. But when it comes to one of the most critical forms of modern communication—live video—many site owners hit a wall. You're often left juggling clunky Zoom links, sending your users to third-party platforms, and sacrificing the professional, branded experience your website was designed to provide.</p>
<p>What if you could integrate a powerful, secure, and fully-branded video conferencing solution directly into your WordPress site as easily as you add a new page?</p>
<p>This is not just possible; it's simple. This guide will show you how to use the <strong>Plug-N-Meet plugin</strong> to transform your WordPress site into a true communication hub, whether you need a simple meeting room, a platform for webinars, or a full-fledged, monetized video service.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="why-self-hosted-is-the-smart-choice-for-wordpress">Why Self-Hosted is the Smart Choice for WordPress<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#why-self-hosted-is-the-smart-choice-for-wordpress" class="hash-link" aria-label="Direct link to Why Self-Hosted is the Smart Choice for WordPress" title="Direct link to Why Self-Hosted is the Smart Choice for WordPress" translate="no">​</a></h3>
<p>Before we dive into the "how," let's cover the "why." While embedding a Zoom meeting is an option, a self-hosted solution like Plug-N-Meet offers three transformative advantages for any serious WordPress site owner:</p>
<ol>
<li class=""><strong>You Own Your Brand:</strong> Every meeting happens on your own domain, with your logo and your colors. You're not providing free advertising for a SaaS giant; you're reinforcing your own brand's professionalism and trust.</li>
<li class=""><strong>You Control Your Data:</strong> All data—from user information to recordings—stays on your own server. This gives you absolute control over privacy and is critical for compliance in sensitive industries.</li>
<li class=""><strong>You Escape Per-User Pricing:</strong> Instead of paying a fee for every host on your team, you have a predictable, fixed cost for your server. This model rewards your growth instead of penalizing it.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-3-step-guide-to-your-first-wordpress-meeting">The 3-Step Guide to Your First WordPress Meeting<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#the-3-step-guide-to-your-first-wordpress-meeting" class="hash-link" aria-label="Direct link to The 3-Step Guide to Your First WordPress Meeting" title="Direct link to The 3-Step Guide to Your First WordPress Meeting" translate="no">​</a></h3>
<p>Getting started is incredibly fast. You can have your first meeting room live in under 15 minutes.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-1-install-the-engine-your-plug-n-meet-server">Step 1: Install the Engine (Your Plug-N-Meet Server)<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#step-1-install-the-engine-your-plug-n-meet-server" class="hash-link" aria-label="Direct link to Step 1: Install the Engine (Your Plug-N-Meet Server)" title="Direct link to Step 1: Install the Engine (Your Plug-N-Meet Server)" translate="no">​</a></h4>
<p>First, you need the server that will power your video calls. You can either self-host the open-source version with our <strong><a class="" href="https://www.plugnmeet.org/docs/installation">simple installation script</a></strong> or get an instant, managed server from <strong><a href="https://www.plugnmeet.cloud/" target="_blank" rel="noopener noreferrer" class="">plugnmeet Cloud</a></strong>. Both will provide you with an <strong>API Key</strong> and <strong>API Secret</strong>.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-2-install-the-free-plug-n-meet-plugin">Step 2: Install the Free Plug-N-Meet Plugin<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#step-2-install-the-free-plug-n-meet-plugin" class="hash-link" aria-label="Direct link to Step 2: Install the Free Plug-N-Meet Plugin" title="Direct link to Step 2: Install the Free Plug-N-Meet Plugin" translate="no">​</a></h4>
<p>From your WordPress dashboard:</p>
<ol>
<li class="">Go to <strong>Plugins &gt; Add New</strong>.</li>
<li class="">Search for <strong>"plugnmeet"</strong>.</li>
<li class="">Click <strong>Install Now</strong>, then <strong>Activate</strong>.</li>
<li class="">Navigate to the new <strong>Plug-N-Meet &gt; Settings</strong> menu.</li>
<li class="">Enter your <strong>API Key</strong> and <strong>API Secret</strong> and save.</li>
</ol>
<p>Your WordPress site is now fully integrated with your video conferencing engine.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-3-create-and-embed-your-room">Step 3: Create and Embed Your Room<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#step-3-create-and-embed-your-room" class="hash-link" aria-label="Direct link to Step 3: Create and Embed Your Room" title="Direct link to Step 3: Create and Embed Your Room" translate="no">​</a></h4>
<ol>
<li class="">Go to <strong>Plug-N-Meet &gt; Rooms</strong> and click <strong>Add New</strong>.</li>
<li class="">Configure your room's features (e.g., name, max participants, welcome message) and get the <strong>Room ID</strong>.</li>
<li class="">After saving, the plugin will generate a unique <strong>Shortcode</strong> for that room (e.g., <code>[plugnmeet_room_view id="your_room_id"]</code>).</li>
<li class="">Create a new WordPress Page, paste the shortcode into the content, and click <strong>Publish</strong>.</li>
</ol>
<p>That's it. You now have a fully functional, branded video conferencing room running directly on your own website.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="level-up-3-powerful-ways-to-use-video-on-your-wordpress-site">Level Up: 3 Powerful Ways to Use Video on Your WordPress Site<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#level-up-3-powerful-ways-to-use-video-on-your-wordpress-site" class="hash-link" aria-label="Direct link to Level Up: 3 Powerful Ways to Use Video on Your WordPress Site" title="Direct link to Level Up: 3 Powerful Ways to Use Video on Your WordPress Site" translate="no">​</a></h3>
<p>Once you've mastered the basics, you can leverage Plug-N-Meet to power a wide range of professional services.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-1-the-private-coaching-or-consultation-room">Use Case 1: The Private Coaching or Consultation Room<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#use-case-1-the-private-coaching-or-consultation-room" class="hash-link" aria-label="Direct link to Use Case 1: The Private Coaching or Consultation Room" title="Direct link to Use Case 1: The Private Coaching or Consultation Room" translate="no">​</a></h4>
<p>Are you a coach, consultant, or therapist? You can easily create secure, private rooms for your client sessions.</p>
<ul>
<li class=""><strong>How to do it:</strong> Create a separate, password-protected page for each client. Embed the shortcode for a unique meeting room on that page. Only the person with the password can access the session, ensuring complete privacy.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-2-the-community-webinar-or-live-event">Use Case 2: The Community Webinar or Live Event<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#use-case-2-the-community-webinar-or-live-event" class="hash-link" aria-label="Direct link to Use Case 2: The Community Webinar or Live Event" title="Direct link to Use Case 2: The Community Webinar or Live Event" translate="no">​</a></h4>
<p>Want to host a webinar for your audience? Plug-N-Meet is a powerful open-source webinar platform.</p>
<ul>
<li class=""><strong>How to do it:</strong> Create a room and configure its features specifically for a webinar. In the room settings, you can disable webcams and microphones for attendees by default, enable <strong>Cloud Recording</strong>, and use features like <strong>Polling</strong> and <strong>Raise Hand</strong> to manage audience interaction. Embed the room's shortcode on your "Events" page and you're ready to go live.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="use-case-3-the-monetized-membership-site">Use Case 3: The Monetized Membership Site<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#use-case-3-the-monetized-membership-site" class="hash-link" aria-label="Direct link to Use Case 3: The Monetized Membership Site" title="Direct link to Use Case 3: The Monetized Membership Site" translate="no">​</a></h4>
<p>This is where you can turn your video feature into a revenue stream. By combining Plug-N-Meet with a popular membership plugin (like Paid Memberships Pro, MemberPress, or Restrict Content Pro), you can create premium video content exclusively for paying subscribers. For a detailed walkthrough of this business model, see our guide on <strong><a class="" href="https://www.plugnmeet.org/blog/no-code-video-conferencing-service-with-wordpress">How to Launch Your Own No-Code Video Conferencing Service with WordPress</a></strong>.</p>
<ul>
<li class=""><strong>How to do it:</strong>
<ol>
<li class="">Use your membership plugin to create a "Premium" subscription tier.</li>
<li class="">Create a new WordPress page for your premium content (e.g., "Weekly Mastermind Call").</li>
<li class="">Paste the Plug-N-Meet room shortcode onto this page.</li>
<li class="">Use your membership plugin's settings to <strong>restrict access</strong> to this page so that only users with an active "Premium" subscription can view it.</li>
</ol>
</li>
</ul>
<p>Now, you have a secure, automated paywall for your exclusive video content.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="unlocking-advanced-features-ai-and-zero-trust-security">Unlocking Advanced Features: AI and Zero-Trust Security<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#unlocking-advanced-features-ai-and-zero-trust-security" class="hash-link" aria-label="Direct link to Unlocking Advanced Features: AI and Zero-Trust Security" title="Direct link to Unlocking Advanced Features: AI and Zero-Trust Security" translate="no">​</a></h3>
<p>The Plug-N-Meet WordPress plugin isn't just for basic meetings. It gives you simple, one-click access to enterprise-grade features directly from your room settings.</p>
<ul>
<li class=""><strong>AI-Powered Meeting Assistants:</strong> Imagine having every meeting automatically transcribed and summarized. With the AI features enabled, you can turn your conversations into actionable intelligence. Learn <strong><a class="" href="https://www.plugnmeet.org/blog/how-to-add-ai-meeting-assistant-features">how to add AI features</a></strong> with a simple toggle.</li>
<li class=""><strong>Zero-Trust Security:</strong> For telehealth, legal consultations, or any conversation requiring absolute confidentiality, you can enable End-to-End Encryption (E2EE). This ensures that not even your server can access the meeting's content. Discover more about our <strong><a class="" href="https://www.plugnmeet.org/blog/privacy-by-design-zero-trust">privacy-by-design philosophy</a></strong>.</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-the-professional-video-solution-wordpress-deserves">Conclusion: The Professional Video Solution WordPress Deserves<a href="https://www.plugnmeet.org/blog/wordpress-video-conferencing-guide#conclusion-the-professional-video-solution-wordpress-deserves" class="hash-link" aria-label="Direct link to Conclusion: The Professional Video Solution WordPress Deserves" title="Direct link to Conclusion: The Professional Video Solution WordPress Deserves" translate="no">​</a></h3>
<p>Stop treating live video as an afterthought. With Plug-N-Meet, you can seamlessly integrate a secure, scalable, and fully-branded video conferencing platform into the WordPress ecosystem you already know and love.</p>
<p>It's a solution that's simple enough to start in minutes but powerful enough to grow with your biggest ambitions.</p>
<hr>
<p><strong>Ready to power up your WordPress site?</strong></p>
<ul>
<li class=""><strong><a href="https://wordpress.org/plugins/plugnmeet/" target="_blank" rel="noopener noreferrer" class="">Download the official WordPress Plugin</a></strong></li>
<li class=""><strong><a href="https://demo.plugnmeet.com/landing.html" target="_blank" rel="noopener noreferrer" class="">Try the Live Demo</a> to see all the features in action.</strong></li>
<li class=""><strong>Follow our <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a> to get your server running.</strong></li>
</ul>]]></content:encoded>
            <category>wordpress</category>
            <category>video-conferencing</category>
            <category>plugin</category>
            <category>no-code</category>
            <category>webinar</category>
            <category>coaching</category>
            <category>membership</category>
            <category>wordpress-video-conferencing</category>
        </item>
        <item>
            <title><![CDATA[A Developer's Guide to Building a Custom Video Chat UI with Plug-N-Meet's Headless API]]></title>
            <link>https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api</link>
            <guid>https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api</guid>
            <pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[You've successfully integrated Plug-N-Meet into your application. You can create rooms, generate join tokens, and embed the client in an `. It's fast and it works. But now you want to go deeper. You want to break out of the ` box and build a truly seamless user experience where the video client feels like a native part of your application's UI.]]></description>
            <content:encoded><![CDATA[<p>You've successfully integrated Plug-N-Meet into your application. You can create rooms, generate join tokens, and embed the client in an <code>&lt;iframe&gt;</code>. It's fast and it works. But now you want to go deeper. You want to break out of the <code>&lt;iframe&gt;</code> box and build a truly seamless user experience where the video client feels like a native part of your application's UI.</p>
<p>This is where Plug-N-Meet's "headless" integration mode comes in.</p>
<p>This guide is for developers who want to move beyond basic embedding. We'll show you how to use the powerful <code>getClientFiles</code> API to render the Plug-N-Meet client directly into your own page's DOM, giving you ultimate control over layout, branding, and the user experience.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-problem-with-iframes">The Problem with Iframes<a href="https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api#the-problem-with-iframes" class="hash-link" aria-label="Direct link to The Problem with Iframes" title="Direct link to The Problem with Iframes" translate="no">​</a></h3>
<p>An <code>&lt;iframe&gt;</code> is a simple way to embed content, but it's essentially a "window" into another website. This creates several limitations for developers aiming for a professional, deeply integrated product:</p>
<ul>
<li class=""><strong>The Black Box:</strong> The <code>&lt;iframe&gt;</code> creates a hard boundary between your parent application and the video client, making communication between them complex and limited.</li>
<li class=""><strong>Layout Restrictions:</strong> You are stuck with the layout provided by the <code>src</code> URL. You can't easily place your own application's components (like a custom header or a sidebar with user data) "inside" the video experience.</li>
<li class=""><strong>Styling and Branding:</strong> While you can pass some branding parameters, your ability to deeply customize the look and feel is limited.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-solution-headless-integration-with-getclientfiles">The Solution: Headless Integration with <code>getClientFiles</code><a href="https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api#the-solution-headless-integration-with-getclientfiles" class="hash-link" aria-label="Direct link to the-solution-headless-integration-with-getclientfiles" title="Direct link to the-solution-headless-integration-with-getclientfiles" translate="no">​</a></h3>
<p>The <code>getClientFiles</code> API is the key to unlocking a truly "headless" video integration. Instead of providing you with a URL to embed, it gives you a list of the exact CSS and JavaScript assets you need to render the client yourself.</p>
<p>This approach transforms Plug-N-Meet from a separate application into a UI component library that you control.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="the-three-step-workflow">The Three-Step Workflow<a href="https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api#the-three-step-workflow" class="hash-link" aria-label="Direct link to The Three-Step Workflow" title="Direct link to The Three-Step Workflow" translate="no">​</a></h4>
<p>The logic is simple and can be implemented in any backend language (PHP, Node.js, Python, etc.).</p>
<ol>
<li class=""><strong>Your Backend Fetches the Asset List:</strong> Your server makes a secure, server-to-server API call to the <code>/auth/getClientFiles</code> endpoint.</li>
<li class=""><strong>Plug-N-Meet Responds with a JSON Object:</strong> This object contains two arrays: <code>css</code> and <code>js</code>, listing the filenames of all the necessary assets.</li>
<li class=""><strong>Your Template Renders the <code>&lt;link&gt;</code> and <code>&lt;script&gt;</code> Tags:</strong> Your server-side templating engine loops through these arrays and dynamically injects the required tags into the HTML page that will be sent to the user's browser.</li>
</ol>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="a-practical-example-nodejs-with-ejs">A Practical Example (Node.js with EJS)<a href="https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api#a-practical-example-nodejs-with-ejs" class="hash-link" aria-label="Direct link to A Practical Example (Node.js with EJS)" title="Direct link to A Practical Example (Node.js with EJS)" translate="no">​</a></h3>
<p>Let's walk through a simplified example using Node.js and the EJS templating engine.</p>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-1-the-backend-route-eg-in-serverjs">Step 1: The Backend Route (e.g., in <code>server.js</code>)<a href="https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api#step-1-the-backend-route-eg-in-serverjs" class="hash-link" aria-label="Direct link to step-1-the-backend-route-eg-in-serverjs" title="Direct link to step-1-the-backend-route-eg-in-serverjs" translate="no">​</a></h4>
<p>This route will handle the API calls and render the final page.</p>
<div class="language-javascript codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-javascript codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">express</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'express'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">PlugNmeet</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'plugnmeet-sdk-js'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">express</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">set</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'view engine'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ejs'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Your Plug-N-Meet credentials (use environment variables in production)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">API_KEY</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'plugnmeet'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">API_SECRET</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'zumyyYWqv7KR2kUqvYdq4z4sXg7XTBD2ljT6'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PLUGNMEET_URL</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'https://demo.plugnmeet.com'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> pnm </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">PlugNmeet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">PLUGNMEET_URL</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">API_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">API_SECRET</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/meeting'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 1. Get the asset files</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> clientFiles </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> pnm</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getClientFiles</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 2. Create a room and generate a join token for the user</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> room </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">room_id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-custom-room'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">metadata</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">room_title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'My Custom UI Room'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">room_features</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token literal-property property" style="color:#36acaa">allow_webcams</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic">// ... other features</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> pnm</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">createRoom</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">room</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> joinToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> pnm</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getJoinToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">room_id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-custom-room'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">user_info</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'John Doe'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">user_id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'user-123'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 3. Define dynamic branding options</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> designOptions </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">primary_color</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'#004D90'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">secondary_color</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'#24AEF7'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">custom_logo</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'https://my-app.com/logo.png'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 4. Render the view, passing all the necessary data</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'meeting'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">plugnmeetUrl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PLUGNMEET_URL</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">clientFiles</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> clientFiles</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">res</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">accessToken</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> joinToken</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">token</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">designOptions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">designOptions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">500</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Error setting up meeting'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Server started on port 3000'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_K79r" id="step-2-the-frontend-template-eg-in-viewsmeetingejs">Step 2: The Frontend Template (e.g., in <code>views/meeting.ejs</code>)<a href="https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api#step-2-the-frontend-template-eg-in-viewsmeetingejs" class="hash-link" aria-label="Direct link to step-2-the-frontend-template-eg-in-viewsmeetingejs" title="Direct link to step-2-the-frontend-template-eg-in-viewsmeetingejs" translate="no">​</a></h4>
<p>This EJS file will take the data from the backend and construct the HTML page.</p>
<div class="language-html codeBlockContainer_aZsx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_F8Nj"><pre tabindex="0" class="prism-code language-html codeBlock_tdCz thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_nvqi"><div class="token-line" style="color:#393A34"><span class="token doctype punctuation" style="color:#393A34;font-style:italic">&lt;!</span><span class="token doctype doctype-tag" style="color:#999988;font-style:italic">DOCTYPE</span><span class="token doctype" style="color:#999988;font-style:italic"> </span><span class="token doctype name" style="color:#999988;font-style:italic">html</span><span class="token doctype punctuation" style="color:#393A34;font-style:italic">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">html</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">lang</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">en</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">head</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">title</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">My Custom Video App</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">title</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Dynamically inject CSS files --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  &lt;% clientFiles.css.forEach(file =&gt; { %&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">link</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">&lt;%= plugnmeetUrl %&gt;/assets/css/&lt;%= file %&gt;</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">rel</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">stylesheet</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  &lt;% }); %&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Inject essential window variables BEFORE loading scripts --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">script</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text/javascript</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript dom variable" style="color:#36acaa">window</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">plugNmeetConfig</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator" style="color:#393A34">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript comment" style="color:#999988;font-style:italic">// Required: The URL of your plugNmeet server.</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript literal-property property" style="color:#36acaa">serverUrl</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">"&lt;%= plugnmeetUrl %&gt;"</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript comment" style="color:#999988;font-style:italic">// Required: The public path to the assets directory.</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript literal-property property" style="color:#36acaa">staticAssetsPath</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">"&lt;%= plugnmeetUrl %&gt;/assets"</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript comment" style="color:#999988;font-style:italic">// Required: Pass the design customization options.</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript literal-property property" style="color:#36acaa">designCustomization</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator" style="color:#393A34">&lt;</span><span class="token script language-javascript operator" style="color:#393A34">%</span><span class="token script language-javascript operator" style="color:#393A34">-</span><span class="token script language-javascript"> designOptions </span><span class="token script language-javascript operator" style="color:#393A34">%</span><span class="token script language-javascript operator" style="color:#393A34">&gt;</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript comment" style="color:#999988;font-style:italic">// Optional: Add any other custom configurations.</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript comment" style="color:#999988;font-style:italic">// See: https://github.com/mynaparrot/plugNmeet-client/blob/main/src/assets/config_sample.js</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript literal-property property" style="color:#36acaa">enableSimulcast</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript boolean" style="color:#36acaa">true</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript literal-property property" style="color:#36acaa">enableDynacast</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript boolean" style="color:#36acaa">true</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript punctuation" style="color:#393A34">;</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">script</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">head</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Your Custom Application UI --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">header</span><span class="token tag" style="color:#00009f"> </span><span class="token tag special-attr attr-name" style="color:#00a4db">style</span><span class="token tag special-attr attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">background</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css hexcode color" style="color:#e3116c">#0A1929</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">color</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css color" style="color:#e3116c">white</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">padding</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css number" style="color:#36acaa">1</span><span class="token tag special-attr attr-value value css language-css unit" style="color:#e3116c">rem</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">My Application Header</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">header</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag special-attr attr-name" style="color:#00a4db">style</span><span class="token tag special-attr attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">display</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> flex</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- The Plug-N-Meet client will mount itself in this div --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">plugNmeet-app</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag special-attr attr-name" style="color:#00a4db">style</span><span class="token tag special-attr attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">flex-grow</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css number" style="color:#36acaa">1</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">height</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css number" style="color:#36acaa">90</span><span class="token tag special-attr attr-value value css language-css unit" style="color:#e3116c">vh</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Your Custom Application Sidebar --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">aside</span><span class="token tag" style="color:#00009f"> </span><span class="token tag special-attr attr-name" style="color:#00a4db">style</span><span class="token tag special-attr attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">width</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css number" style="color:#36acaa">250</span><span class="token tag special-attr attr-value value css language-css unit" style="color:#e3116c">px</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">background</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css hexcode color" style="color:#e3116c">#f4f4f4</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css property" style="color:#36acaa">padding</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">:</span><span class="token tag special-attr attr-value value css language-css" style="color:#e3116c"> </span><span class="token tag special-attr attr-value value css language-css number" style="color:#36acaa">1</span><span class="token tag special-attr attr-value value css language-css unit" style="color:#e3116c">rem</span><span class="token tag special-attr attr-value value css language-css punctuation" style="color:#393A34">;</span><span class="token tag special-attr attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h2</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">Meeting Notes</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h2</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">This sidebar is part of my parent application!</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">aside</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Dynamically inject JavaScript files --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  &lt;% clientFiles.js.forEach(file =&gt; { %&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;% if (file.startsWith('main-module.')) { %&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">script</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">&lt;%= plugnmeetUrl %&gt;/assets/js/&lt;%= file %&gt;</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">module</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">script</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;% } else { %&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">script</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">&lt;%= plugnmeetUrl %&gt;/assets/js/&lt;%= file %&gt;</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">defer</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">script</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;% } %&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  &lt;% }); %&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">script</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript comment" style="color:#999988;font-style:italic">// Add the access token to the URL without reloading the page</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript keyword" style="color:#00009f">const</span><span class="token script language-javascript"> url </span><span class="token script language-javascript operator" style="color:#393A34">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword" style="color:#00009f">new</span><span class="token script language-javascript"> </span><span class="token script language-javascript class-name">URL</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript dom variable" style="color:#36acaa">window</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">location</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">;</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    url</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">searchParams</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript method function property-access" style="color:#d73a49">set</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript string" style="color:#e3116c">'access_token'</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">'&lt;%= accessToken %&gt;'</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">;</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript dom variable" style="color:#36acaa">window</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">history</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript method function property-access" style="color:#d73a49">pushState</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">''</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"> url</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">;</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">script</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">html</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-result-a-truly-integrated-experience">The Result: A Truly Integrated Experience<a href="https://www.plugnmeet.org/blog/developer-guide-custom-video-chat-ui-headless-api#the-result-a-truly-integrated-experience" class="hash-link" aria-label="Direct link to The Result: A Truly Integrated Experience" title="Direct link to The Result: A Truly Integrated Experience" translate="no">​</a></h3>
<p>When a user visits <code>/meeting</code>, they will see your application's custom header and sidebar, with the Plug-N-Meet video client rendered seamlessly in the middle. The client will be styled with the dynamic brand colors you passed from the backend.</p>
<p>You have successfully broken out of the <code>&lt;iframe&gt;</code> box.</p>
<p>From here, the possibilities are endless. You can use your own application's state to interact with the meeting, build custom layouts that respond to meeting events, and create a user experience that is completely unique to your product.</p>
<p>This is the true power of a headless, API-first platform. It gives you the building blocks not just to add a feature, but to build a deeply integrated and professional product.</p>
<hr>
<p><strong>Ready to build your custom UI?</strong></p>
<ul>
<li class=""><strong>Review the full <a class="" href="https://www.plugnmeet.org/docs/api/get-client-files"><code>getClientFiles</code> API Documentation</a>.</strong></li>
<li class=""><strong>Explore our <a class="" href="https://www.plugnmeet.org/docs/developer-guide/design-customisation">Design Customization Guide</a> for more branding options.</strong></li>
<li class=""><strong>Check out our official <a href="https://github.com/mynaparrot/plugNmeet-sdk-js" target="_blank" rel="noopener noreferrer" class="">JavaScript SDK</a> to simplify your backend code.</strong></li>
</ul>]]></content:encoded>
            <category>developer</category>
            <category>tutorial</category>
            <category>how-to</category>
            <category>api</category>
            <category>headless</category>
            <category>getClientFiles</category>
            <category>white-label</category>
            <category>customization</category>
            <category>javascript</category>
        </item>
        <item>
            <title><![CDATA[Scaling Your Video Service: How Plug-N-Meet's Architecture Saves You Money]]></title>
            <link>https://www.plugnmeet.org/blog/scaling-architecture-saves-money</link>
            <guid>https://www.plugnmeet.org/blog/scaling-architecture-saves-money</guid>
            <pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Your video service is a hit. User numbers are climbing, and engagement is high. But as you celebrate your success, a new problem emerges: your server bill is exploding. To keep up with demand, you keep buying bigger and bigger servers, paying a premium for resources that sit idle most of the day.]]></description>
            <content:encoded><![CDATA[<p>Your video service is a hit. User numbers are climbing, and engagement is high. But as you celebrate your success, a new problem emerges: your server bill is exploding. To keep up with demand, you keep buying bigger and bigger servers, paying a premium for resources that sit idle most of the day.</p>
<p>What went wrong? You've fallen into the monolith trap.</p>
<p>This post explains how Plug-N-Meet's modern, decoupled architecture is designed to prevent this exact problem, enabling you to scale your service efficiently and control your costs. For a business, this isn't just a technical detail—it's a core financial advantage.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-monolith-trap-paying-for-empty-seats">The Monolith Trap: Paying for Empty Seats<a href="https://www.plugnmeet.org/blog/scaling-architecture-saves-money#the-monolith-trap-paying-for-empty-seats" class="hash-link" aria-label="Direct link to The Monolith Trap: Paying for Empty Seats" title="Direct link to The Monolith Trap: Paying for Empty Seats" translate="no">​</a></h3>
<p>Many traditional video conferencing platforms are built as <strong>monoliths</strong>. This means the core application, the media processing, and the recording services are all bundled into a single, massive application.</p>
<p>This creates a huge problem when it's time to scale:</p>
<ul>
<li class=""><strong>If you need to support more concurrent users</strong>, you have to upgrade the <em>entire</em> server, even if your recording service is barely being used.</li>
<li class=""><strong>If a few large recording jobs are using all the CPU</strong>, your live meetings will lag and stutter, forcing you to buy a bigger server just to handle a temporary workload.</li>
</ul>
<p>A monolithic server is like renting a giant bus for a single passenger. You pay for all the empty seats, and if one part of the bus breaks down, the whole trip is canceled.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="the-plug-n-meet-way-a-decoupled-intelligent-architecture">The Plug-N-Meet Way: A Decoupled, Intelligent Architecture<a href="https://www.plugnmeet.org/blog/scaling-architecture-saves-money#the-plug-n-meet-way-a-decoupled-intelligent-architecture" class="hash-link" aria-label="Direct link to The Plug-N-Meet Way: A Decoupled, Intelligent Architecture" title="Direct link to The Plug-N-Meet Way: A Decoupled, Intelligent Architecture" translate="no">​</a></h3>
<p>Plug-N-Meet was designed from the ground up to avoid this trap. It is not one giant application; it is a set of specialized, independent services that work together.</p>
<p>The three core components are:</p>
<ol>
<li class=""><strong><code>plugnmeet-server</code> (The Brain):</strong> A lightweight Go application that handles all the API calls, authentication, and business logic.</li>
<li class=""><strong><code>LiveKit</code> (The Media Highway):</strong> A high-performance, open-source media server that manages all the real-time audio and video traffic.</li>
<li class=""><strong><code>plugnmeet-recorder</code> (The Factory):</strong> A dedicated Go application that handles CPU-intensive tasks like MP4 recording and RTMP broadcasting.</li>
</ol>
<p>These components are designed to be scaled <strong>independently</strong>. This is the key to cost-effective growth.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="benefit-1-scale-only-what-you-need-elastic-scaling">Benefit 1: Scale Only What You Need (Elastic Scaling)<a href="https://www.plugnmeet.org/blog/scaling-architecture-saves-money#benefit-1-scale-only-what-you-need-elastic-scaling" class="hash-link" aria-label="Direct link to Benefit 1: Scale Only What You Need (Elastic Scaling)" title="Direct link to Benefit 1: Scale Only What You Need (Elastic Scaling)" translate="no">​</a></h3>
<p>With a decoupled architecture, you can scale each component based on your actual needs.</p>
<ul>
<li class=""><strong>Expecting a huge webinar?</strong> You don't need to touch your main application server. Simply spin up a few extra <code>LiveKit</code> media servers for the duration of the event, then spin them down afterward.</li>
<li class=""><strong>Seeing more users in a specific region?</strong> Deploy media servers in that geographic area to reduce latency, without having to replicate your entire backend.</li>
</ul>
<p>This is like adding more cashiers to a store only during the holiday rush. You get the performance you need when you need it, and you don't pay for idle resources during quiet periods.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_K79r" id="benefit-2-isolate-your-workloads-improved-quality-of-service">Benefit 2: Isolate Your Workloads (Improved Quality of Service)<a href="https://www.plugnmeet.org/blog/scaling-architecture-saves-money#benefit-2-isolate-your-workloads-improved-quality-of-service" class="hash-link" aria-label="Direct link to Benefit 2: Isolate Your Workloads (Improved Quality of Service)" title="Direct link to Benefit 2: Isolate Your Workloads (Improved Quality of Service)" translate="no">​</a></h3>
<p>In a monolith, a resource-hungry task can degrade the entire user experience. If someone starts a large recording, everyone's live video call could suffer.</p>
<p>Plug-N-Meet's architecture prevents this. Because the <code>plugnmeet-recorder</code> is a separate service, you can run it on entirely different machines from your live media servers.</p>
<ul>
<li class=""><strong>The Result:</strong> A CPU-intensive MP4 transcoding job will have <strong>zero impact</strong> on the quality and stability of your live meetings.</li>
<li class=""><strong>The Benefit:</strong> Your users always get a smooth, high-quality experience, which is critical for customer satisfaction and retention.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_K79r" id="conclusion-a-lower-total-cost-of-ownership-tco">Conclusion: A Lower Total Cost of Ownership (TCO)<a href="https://www.plugnmeet.org/blog/scaling-architecture-saves-money#conclusion-a-lower-total-cost-of-ownership-tco" class="hash-link" aria-label="Direct link to Conclusion: A Lower Total Cost of Ownership (TCO)" title="Direct link to Conclusion: A Lower Total Cost of Ownership (TCO)" translate="no">​</a></h2>
<p>"Scalable" isn't just a technical buzzword; it's a business feature. The ability to scale components independently and isolate workloads means you are not constantly over-provisioning your hardware. You pay only for the resources you actually use.</p>
<p>As your platform grows, this intelligent design leads to a dramatically <strong>lower Total Cost of Ownership (TCO)</strong> compared to a monolithic solution. You can grow your user base sustainably without your infrastructure costs spiraling out of control.</p>
<p>Plug-N-Meet's architecture isn't just a technical implementation detail; it's a strategic advantage designed to help your business succeed at scale.</p>
<hr>
<p><strong>Ready to learn more about scaling?</strong></p>
<ul>
<li class=""><strong>Read our detailed <a class="" href="https://www.plugnmeet.org/docs/developer-guide/scalable-setup">Scalable Deployment Guide</a></strong></li>
<li class=""><strong>Dive deep into our <a class="" href="https://www.plugnmeet.org/blog/backend-architecture-deep-dive">Backend Architecture</a></strong></li>
<li class=""><strong>Get started with the <a class="" href="https://www.plugnmeet.org/docs/installation">Installation Guide</a></strong></li>
</ul>]]></content:encoded>
            <category>scaling</category>
            <category>architecture</category>
            <category>tco</category>
            <category>cost-saving</category>
            <category>self-hosting</category>
            <category>business</category>
            <category>developer</category>
        </item>
    </channel>
</rss>