How to Create a Custom Platform Backend for Dear ImGui: A Complete Implementation Guide

Dear ImGui separates platform concerns (window management, input handling) from rendering by requiring backends to populate the ImGuiIO structure with display metrics, translate native OS events into ImGui input calls, and manage platform-specific resources through a dedicated backend data structure.

Dear ImGui (ocornut/imgui) uses a platform-agnostic core that delegates all window system interactions to pluggable backends. Creating a custom platform backend involves implementing a minimal C++ interface that bridges your native windowing system with ImGui's input abstraction layer. This guide references the actual source code in the official repository to show you exactly how to build a production-ready backend.

Understanding the Platform Backend Architecture

A platform backend acts as a translation layer between your operating system's windowing API and Dear ImGui's internal input system. The architecture centers on the ImGuiIO structure, which you populate each frame with timing data, display dimensions, and input events.

Every platform backend must maintain a backend data structure (following the pattern of ImGui_ImplWin32_Data in imgui_impl_win32.cpp) that persists between frames. This struct stores native window handles, timing counters, mouse state, and optional gamepad handles. You attach this structure to io.BackendPlatformUserData during initialization and clear it during shutdown.

The backend also advertises its capabilities through ImGuiBackendFlags. Common flags include ImGuiBackendFlags_HasMouseCursors and ImGuiBackendFlags_HasSetMousePos, which you set in io.BackendFlags during initialization to inform ImGui that your backend can manipulate the system cursor.

Step 1: Create the Backend Data Structure

Define a data structure that holds all platform-specific state. This pattern appears consistently across official backends like imgui_impl_win32.cpp (lines 62-80) and imgui_impl_glfw.cpp.

// imgui_impl_mybackend.h
#pragma once
#include "imgui.h"

struct MyBackendData
{
    MyWindowHandle  Window;
    double          Time;
    float           MousePos[2];
    bool            MouseButtons[5];
    // Add clipboard handles, cursor handles, etc.
};

// Function declarations
IMGUI_IMPL_API bool ImGui_ImplMyBackend_Init(void* native_window);
IMGUI_IMPL_API void ImGui_ImplMyBackend_Shutdown();
IMGUI_IMPL_API void ImGui_ImplMyBackend_NewFrame();
IMGUI_IMPL_API void ImGui_ImplMyBackend_HandleEvent(const MyEvent& ev);

Store this structure in io.BackendPlatformUserData to make your backend re-entrant for multiple ImGui contexts.

Step 2: Implement Initialization

The initialization function must allocate your backend data, set the backend name, and advertise supported capabilities. Reference imgui_impl_win32.cpp (lines 62-80) for the exact initialization sequence.

bool ImGui_ImplMyBackend_Init(void* native_window)
{
    ImGuiIO& io = ImGui::GetIO();
    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized!");
    
    MyBackendData* bd = IM_NEW(MyBackendData)();
    io.BackendPlatformUserData = (void*)bd;
    io.BackendPlatformName = "imgui_impl_mybackend";
    
    // Advertise capabilities
    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;
    
    bd->Window = (MyWindowHandle)native_window;
    bd->Time = GetPlatformTime(); // Platform-specific timer
    
    return true;
}

Step 3: Handle Per-Frame Updates

Each frame, the backend must update timing information, display size, and mouse cursor state. The NewFrame function in imgui_impl_win32.cpp (lines 219-267) demonstrates this pattern.

void ImGui_ImplMyBackend_NewFrame()
{
    ImGuiIO& io = ImGui::GetIO();
    MyBackendData* bd = (MyBackendData*)io.BackendPlatformUserData;
    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplMyBackend_Init()?");
    
    // Update display size
    io.DisplaySize = GetWindowSize(bd->Window);
    
    // Update delta time
    double current_time = GetPlatformTime();
    io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f/60.0f);
    bd->Time = current_time;
    
    // Update mouse cursor if requested
    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
        return;
    
    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
        HideSystemCursor();
    else
        SetSystemCursor(imgui_cursor);
}

Step 4: Translate Native Events

The critical responsibility is converting your platform's native events into ImGui input calls. Create a handler function that your application calls for each window event, following the event translation patterns in imgui_impl_glfw.cpp and imgui_impl_sdl2.cpp.

void ImGui_ImplMyBackend_HandleEvent(const MyEvent& ev)
{
    ImGuiIO& io = ImGui::GetIO();
    
    switch (ev.type)
    {
        case MyEvent::MouseMove:
            io.AddMousePosEvent((float)ev.x, (float)ev.y);
            break;
        case MyEvent::MouseButtonDown:
            io.AddMouseButtonEvent(ev.button, true);
            break;
        case MyEvent::MouseButtonUp:
            io.AddMouseButtonEvent(ev.button, false);
            break;
        case MyEvent::MouseWheel:
            io.AddMouseWheelEvent(ev.wheel_x, ev.wheel_y);
            break;
        case MyEvent::KeyDown:
            io.AddKeyEvent(ev.key, true);
            break;
        case MyEvent::KeyUp:
            io.AddKeyEvent(ev.key, false);
            break;
        case MyEvent::Char:
            io.AddInputCharacterUTF16(ev.codepoint);
            break;
    }
}

Call io.AddInputCharacterUTF16() for text input rather than direct key events to properly handle international keyboards and dead keys.

Step 5: Implement Shutdown

Cleanup must release all allocated resources and clear backend-specific ImGuiIO fields to prevent dangling pointers. Reference imgui_impl_win32.cpp (lines 400-426).

void ImGui_ImplMyBackend_Shutdown()
{
    ImGuiIO& io = ImGui::GetIO();
    MyBackendData* bd = (MyBackendData*)io.BackendPlatformUserData;
    
    if (bd)
    {
        // Free cursors, unload DLLs, etc.
        IM_DELETE(bd);
        io.BackendPlatformUserData = nullptr;
        io.BackendPlatformName = nullptr;
        io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos);
    }
}

Step 6: Integrate Into Your Main Loop

Wire the backend into your application's event loop, ensuring correct ordering of ImGui function calls. The examples/ folder in the repository contains reference implementations like example_win32_opengl3/main.cpp.

// Initialization
MyWindow* window = CreateNativeWindow();
ImGui_ImplMyBackend_Init(window);
ImGui_ImplOpenGL3_Init("#version 330");

// Main loop
while (!ShouldClose(window))
{
    // Process events - forward to backend handler
    PollEvents(window, ImGui_ImplMyBackend_HandleEvent);
    
    // Start ImGui frame
    ImGui_ImplMyBackend_NewFrame();
    ImGui_ImplOpenGL3_NewFrame();
    ImGui::NewFrame();
    
    // Build UI here
    ImGui::ShowDemoWindow();
    
    // Render
    ImGui::Render();
    int display_w, display_h;
    GetFramebufferSize(window, &display_w, &display_h);
    glViewport(0, 0, display_w, display_h);
    glClear(GL_COLOR_BUFFER_BIT);
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
    SwapBuffers(window);
}

// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplMyBackend_Shutdown();

Optional: Clipboard, Gamepad, and Multi-Viewport

Clipboard support requires setting io.SetClipboardTextFn and io.GetClipboardTextFn callbacks. The Win32 backend (lines 120-150) shows a reference implementation using native clipboard APIs.

Gamepad support involves loading XInput or similar libraries dynamically and pushing controller states via io.AddKeyEvent() for mapped buttons and io.AddAnalogEvent() for axes. See the #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD section in imgui_impl_win32.cpp.

Multi-viewport support requires additional ImGuiPlatformIO callbacks: Platform_CreateWindow, Platform_DestroyWindow, and Platform_ShowWindow. These enable ImGui to create floating tool windows outside the main application window. The Win32 backend (lines 300-340) contains the platform window management implementation.

Summary

  • Backend data persistence: Store platform state in a dedicated structure attached to io.BackendPlatformUserData, following the ImGui_ImplWin32_Data pattern.
  • Capability advertisement: Set ImGuiBackendFlags in io.BackendFlags during initialization to indicate support for mouse cursors and cursor positioning.
  • Event translation: Convert native window events to ImGui input using io.AddMousePosEvent(), io.AddKeyEvent(), and io.AddInputCharacterUTF16().
  • Frame updates: Update io.DisplaySize and io.DeltaTime every frame in your NewFrame function.
  • Resource cleanup: Release all handles and clear ImGuiIO backend-specific fields in your Shutdown function.

Frequently Asked Questions

What is the difference between a platform backend and a renderer backend in Dear ImGui?

A platform backend handles window system integration—managing input events, window focus, clipboard, and timing—while a renderer backend handles graphics API calls to draw ImGui's vertex buffers. You typically combine one platform backend (e.g., Win32, GLFW, SDL) with one renderer backend (e.g., OpenGL3, DirectX11, Vulkan) to create a complete implementation.

How do I handle high-DPI displays in a custom platform backend?

Update io.DisplayFramebufferScale with the appropriate DPI scaling factors (e.g., obtained from GetDpiForWindow on Windows or glfwGetMonitorContentScale on GLFW). ImGui uses this scale to adjust font rasterization and vertex projection. You must also scale io.DisplaySize by the inverse of the framebuffer scale so that ImGui operates in logical screen coordinates.

Can I support multiple windows with one platform backend?

Yes, but you must implement multi-viewport support by populating the ImGuiPlatformIO structure with callbacks for Platform_CreateWindow, Platform_DestroyWindow, and Platform_ShowWindow. The backend data structure should track multiple window handles, and you must ensure each viewport's PlatformUserData points to the correct native window. Reference imgui_impl_win32.cpp (lines 300-340) for the platform window management implementation.

What is the correct order of function calls in the main loop?

First, poll and translate native events using your backend's event handler. Second, call your platform backend's NewFrame function to update io.DeltaTime and io.DisplaySize. Third, call the renderer backend's NewFrame function. Fourth, call ImGui::NewFrame(). After building the UI, call ImGui::Render() followed by the renderer's RenderDrawData function. Shutdown must occur in reverse order: renderer first, then platform.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →