You can turn on developer mode to catch mistakes when using the JavaScript API, the DOM API, and data bindings.

By default, these mistakes fail safely and silently— misspelled binding names, markup path typos, and calls on missing elements do nothing. Developer mode reports each mistake as it happens so you can fix it during development.

## Turning On Developer Mode

To turn on developer mode, set `Config::diagnostics.developer_mode` to `true` before creating the renderer (or pass the config to `App::Create()` when using AppCore).

<!-- tabs:start -->
```cpp
Config config;
#ifndef NDEBUG
config.diagnostics.developer_mode = true;  // development builds only
#endif

Platform::instance().set_config(config);
RefPtr<Renderer> renderer = Renderer::Create();
```

```c
ULConfig config = ulCreateConfig();
#ifndef NDEBUG
ulConfigSetDeveloperMode(config, true);  // development builds only
#endif

ULRenderer renderer = ulCreateRenderer(config);
ulDestroyConfig(config);
```
<!-- tabs:end -->

> 🚧 Turn Off in Shipped Builds
>
> Leave developer mode off in shipped builds. Running with diagnostics on slows down property reads on JavaScript API namespace objects. Shipped builds also ignore diagnostics environment variables when developer mode is off.

## Reading Warnings

Diagnostics are sent to the `Logger` at `LogLevel::Warning`. Each message begins with an API tag such as `[js]` for the JavaScript API, `[dom]` for the DOM API, or `[data]` for data bindings.

If a script on the page accesses an undefined property on an exposed API:

```js
console.log(myApp.verison);  // a typo for myApp.version
```

The library logs a warning with the suggested spelling:

```text
[js] 'myApp.verison' is not defined. Did you mean 'version'?
```

When markup binds to a property that does not exist on a bound type:

```html
<p>{{player.nmae}}</p>
```

The library logs the unmatched path:

```text
[data] {{player.nmae}}: 'Player' has no field 'nmae'.
```

### Where Warnings Appear

The library sends every warning to the logger— warnings tied to a specific page also appear in that page's console.

| Destination | Details |
| :--- | :--- |
| `ultralight.log` | Written by AppCore for applications that call `App::Create()`. The log is saved in the per-user app data folder configured by `Settings::developer_name` and `app_name` rather than beside the executable. See [Logging and Console Messages](/docs/2.0/logging-and-console-messages). |
| `Logger` | Used by applications that call `Renderer::Create()`. If you don't register a `Logger`, the library produces no log output. |
| Page console | View messages in the Web Inspector or receive them through `ViewListener::OnAddConsoleMessage()` with source `kMessageSource_NativeAPI`. Warnings tied to a specific page (such as misspelled exposed properties, markup path typos, or rejected style values) appear here across all API families. Setup and handle warnings (such as a failed attach, a malformed selector, or a call on a handle after a page navigates away) go to the logger only. |

## Setting Diagnostics Levels

You can configure diagnostics per API family using the `DiagnosticsLevel` enum. Both `javascript` and `dom` in `Config::diagnostics` default to `DiagnosticsLevel::Auto`.

| Level | Reports |
| :--- | :--- |
| `DiagnosticsLevel::Off` | No per-operation warnings. Setup mistakes, such as a failed attach or a malformed listener selector, still warn at every level. |
| `DiagnosticsLevel::Warn` | Reports each mistake as it happens. |
| `DiagnosticsLevel::Strict` | Everything reported by `Warn`, plus stricter checks. Passing extra arguments to an exposed function raises a `TypeError` on the page, and every DOM call on an empty handle logs a warning. |
| `DiagnosticsLevel::Auto` | Uses `Warn` when developer mode is on, and `Off` when developer mode is off (the default). |

The `dom` field configures checks for both the DOM API and data bindings, while `javascript` configures the JavaScript API.

To set the DOM and data-binding diagnostics level to strict:

<!-- tabs:start -->
```cpp
config.diagnostics.dom = DiagnosticsLevel::Strict;
```

```c
ulConfigSetDOMDiagnostics(config, kULDiagnosticsLevel_Strict);
```
<!-- tabs:end -->

> 👍 Catch Calls That Have No Effect
>
> If a DOM call has no effect, set the DOM level to `DiagnosticsLevel::Strict`. The library logs a warning for each call on an empty handle (eg, from a misspelled selector or an element that isn't on the page). This check applies only to the DOM API— data bindings already report markup mistakes at `DiagnosticsLevel::Warn`, so `DiagnosticsLevel::Strict` adds nothing for them.

### Overriding Levels from the Environment

You can override diagnostics levels at launch without recompiling by setting environment variables.

Set `UL_JS_DIAGNOSTICS` for the JavaScript API and `UL_DOM_DIAGNOSTICS` for the DOM API and data bindings. Each variable accepts `off`, `warn`, or `strict` (case-insensitive).

To launch an application with strict DOM diagnostics from the command line:

```bash
# macOS and Linux:
UL_DOM_DIAGNOSTICS=strict ./MyApp

# Windows (PowerShell):
$env:UL_DOM_DIAGNOSTICS = "strict"; .\MyApp.exe
```

> 📘 Active Only in Developer Mode
>
> The library reads diagnostics environment variables once at startup. These variables are ignored unless developer mode is on.

## Checks Reported by Each API

Each API family checks for common mistakes specific to how native code interacts with the page.

- **JavaScript API** — Reports misspelled property names with suggestions, undeclared events, numbers that lose precision, and pages refused by origin rules or filters. Native code dropping a call result with an unread exception also triggers a warning (uncaught page errors are not reported here). See [JavaScript Errors and Diagnostics](/docs/2.0/javascript-errors-and-diagnostics).
- **DOM API** — Reports failed calls that went unchecked (such as a malformed selector) and calls made on a handle whose page has navigated away or been destroyed. See [DOM Handles and Errors](/docs/2.0/dom-handles-and-errors).
- **Data bindings** — Reports markup mistakes like path typos or style values that a property cannot accept. The rest of the page continues to bind normally. See [Showing Data in Markup](/docs/2.0/showing-data-in-markup).

## Filtering Log Messages

The `Config::diagnostics.min_log_level` setting specifies the lowest severity the library sends to the `Logger`. It defaults to `LogLevel::Info`, which includes warnings and errors while hiding `LogLevel::Debug` messages.

Because diagnostics warnings are logged at `LogLevel::Warning`, setting the minimum level to `LogLevel::Error` or `LogLevel::Fatal` hides them.

To lower the threshold so debug messages also reach the log:

<!-- tabs:start -->
```cpp
config.diagnostics.min_log_level = LogLevel::Debug;
```

```c
ulConfigSetMinLogLevel(config, kLogLevel_Debug);
```
<!-- tabs:end -->
