Ultralight delegates OS tasks (such as file loading, font lookup, clipboard access, logging, GPU rendering, and audio playback) to handler objects registered on the `Platform` singleton— you configure them once before creating the renderer. If you use AppCore, it sets up default platform handlers automatically.

## Registering Platform Handlers

You register each handler on `Platform::instance()` using its dedicated setter method, such as `Platform::set_file_system()` or `Platform::set_font_loader()`.

| Handler | Purpose | With `Renderer::Create()` | Guide |
| --- | --- | --- | --- |
| `FileSystem` | Loads `file:///` URLs and library resource files | Required | [Custom File System](/docs/2.0/custom-filesystem) |
| `FontLoader` | Maps CSS font families to font data | Required | [Custom Font Loading](/docs/2.0/custom-font-loading) |
| `Clipboard` | Handles copy and paste | Optional | [Clipboard Integration](/docs/2.0/clipboard-integration) |
| `Logger` | Receives library log messages | Optional | [Logging and Console Messages](/docs/2.0/logging-and-console-messages) |
| `GPUDriver` | Renders GPU-accelerated Views | Required for accelerated Views | [Implementing a GPU Driver](/docs/2.0/implementing-a-gpudriver) |
| `SurfaceFactory` | Allocates pixel buffers for CPU-rendered Views | Optional (default provided) | [Render Surfaces](/docs/2.0/using-a-custom-surface) |
| `AudioOutput` | Plays audio for `<video>` and `<audio>` | Optional (Pro edition and higher) | [Media and Audio](/docs/2.0/media-and-audio) |
| `ThreadFactory` | Creates the library's threads | Optional | [Custom Threads](/docs/2.0/advanced-platform-hooks) |
| `Profiler` | Receives timing callbacks | Optional (Pro edition and higher) | [Profiling and Tracing](/docs/2.0/profiling-and-tracing) |

> 🚧 Missing Required Handlers
>
> The library logs a `LogLevel::Fatal` message and exits the process if a required handler is missing. This happens at startup if `FileSystem` is missing, and when creating the first View if `FontLoader` is missing. Creating an accelerated View without a `GPUDriver` exits the same way.

## Setting Handlers with AppCore

`App::Create()` installs stock handlers automatically. If you set a handler on `Platform` before calling `App::Create()`, AppCore keeps it and fills in stock handlers for the rest.

> 🚧 Custom Render Handlers with AppCore
>
> AppCore keeps a `GPUDriver` or `SurfaceFactory` set before `App::Create()`, but its windows can't present Views with it. You can set any other handler beforehand.

## Using Stock Handlers

If you manage your own run loop and call `Renderer::Create()`, you can link AppCore to use its stock handlers declared in `<AppCore/Platform.h>` (the C version sits in the tab below).

<!-- tabs:start -->
```cpp
#include <Ultralight/Ultralight.h>
#include <AppCore/Platform.h>

using namespace ultralight;

void InitPlatform() {
  ///
  /// Use the OS's native font loader.
  ///
  Platform::instance().set_font_loader(GetPlatformFontLoader());

  ///
  /// Use the OS's native file loader, with a base directory of "."
  /// All file:/// URLs will load relative to this base directory.
  ///
  Platform::instance().set_file_system(GetPlatformFileSystem("."));

  ///
  /// Use the default logger (writes to a log file, the library owns it).
  ///
  Platform::instance().set_logger(GetDefaultLogger("ultralight.log"));
}
```
```c
#include <AppCore/CAPI.h>

void InitPlatform(void) {
  ///
  /// Use the OS's native font loader.
  ///
  ulEnablePlatformFontLoader();

  ///
  /// Use the OS's native file loader, with a base directory of "."
  /// All file:/// URLs will load relative to this base directory.
  ///
  ULString base_dir = ulCreateString(".");
  ulEnablePlatformFileSystem(base_dir);
  ulDestroyString(base_dir);

  ///
  /// Use the default logger (writes to a log file, the library owns it).
  ///
  ULString log_path = ulCreateString("ultralight.log");
  ulEnableDefaultLogger(log_path);
  ulDestroyString(log_path);
}
```
<!-- tabs:end -->

The library owns the handlers returned by these helper functions— you must never destroy them.

The SDK's `platform/` folder contains the source code for each stock handler under the Zlib license, which you can use as a starting point for custom handlers (see [Linking to the Library](/docs/2.0/linking-to-the-library)).

## Setup Order for Renderer::Create()

When you create the renderer directly, set up the platform in this order:

1. Call `Platform::instance().set_config()` with a `Config` (see [Creating the Renderer](/docs/2.0/creating-the-renderer#content-configuring-the-library)).
2. Register platform handlers on `Platform::instance()`.
3. Call `Renderer::Create()` (see [Your First Game UI](/docs/2.0/your-first-game-ui)).

## Writing Custom Handlers

To write a custom handler in C++, inherit from the interface class, implement its virtual methods, and pass a pointer to the matching setter on `Platform::instance()`.

You retain ownership of custom handlers passed to `Platform`— each instance must outlive the `Renderer`.

The library may call handler methods from threads other than the Renderer's thread. Each handler's guide details which calls run concurrently and must be thread-safe.

In C, platform handlers use structs of function pointers instead of C++ classes— see [Platform Handlers in C](/docs/2.0/c-platform-handlers).
