MQTT
The mqtt feature provides client and adapter implementations on top of the MQTT pub/sub protocol. This enables:
- Broker-mediated connections: Connect Unreal applications through any MQTT-compliant broker (Mosquitto, EMQX, HiveMQ, AWS IoT Core, etc.)
- Cross-platform fan-out: Communicate with Python services, JavaScript dashboards, mobile apps, and embedded devices that all speak MQTT
- IoT and telemetry: Stream data into and out of Unreal alongside the broader IoT ecosystem
- Many-to-many communication: One adapter can serve many clients, and any client can subscribe to topics from many adapters
Use an MQTT client instead of your local implementation to consume a remote service over MQTT. Use an MQTT adapter to expose your local implementation to MQTT subscribers.
Unlike OLink (point-to-point WebSocket) and Message Bus (intra-Unreal IPC), MQTT requires an external broker. The broker decouples publishers from subscribers and handles message routing, retention, and last-will semantics.
When to use MQTT vs OLink vs Message Bus
| Feature | MQTT | OLink | Message Bus |
|---|---|---|---|
| Protocol | MQTT 5 over TCP / TLS | WebSocket (ObjectLink) | Unreal's UDP Message Bus |
| Topology | Pub/sub via broker | Direct request/response | Local UDP discovery |
| Best for | IoT, telemetry, many-to-many | Tight Unreal-to-service link | Unreal-to-Unreal IPC |
| Setup | Requires broker URL | Requires server URL | Zero configuration on localhost |
| Network | Any network with broker reachable | Any network | Local network / same machine |
| Non-Unreal peers | Yes (Python, JS, mobile, embedded) | Yes (Qt, C++, simulation) | No |
| Retained state | Yes (broker retains last property) | No (live link only) | No (heartbeat-based) |
| ApiGear Simulation | No | Yes | No |
Use MQTT when fanning state out to many heterogeneous subscribers or integrating with an existing IoT topology. Use OLink for direct Unreal-to-service or simulation connections. Use Message Bus for IPC between Unreal applications.
ApiGear MQTT Protocol
The generated code maps interface concepts to MQTT topics using the following convention. For an interface Hello in module io.world:
| Direction | Topic | Purpose |
|---|---|---|
| Client → Adapter | io.world/Hello/set/last | Set property request |
| Adapter → Client | io.world/Hello/prop/last | Property change notification (retained) |
| Client → Adapter | io.world/Hello/rpc/say | Operation invocation |
| Adapter → Client | io.world/Hello/rpc/say/{clientId}/result | Operation reply (per-client) |
| Adapter → Client | io.world/Hello/sig/justSaid | Signal broadcast |
Property notifications are published with retain=true so newly subscribing clients receive the current state immediately. Operation replies are routed back to the topic the caller advertised in the MQTT 5 ResponseTopic property — typically …/rpc/{op}/{clientId}/result — so multiple clients can invoke the same operation concurrently without their replies colliding.
Payloads are JSON-encoded. RPC payloads contain only the operation's args (e.g. [42]); correlation rides on the MQTT 5 CorrelationData property as opaque bytes that the service echoes back unchanged in the reply. Property values and signal arg arrays are unchanged plain JSON. All subscribe and publish operations use QoS 1 (at-least-once delivery) by default.
File overview for module
With our example API definition:
Hello World API (click to expand)
schema: apigear.module/1.0
name: io.world
version: "1.0.0"
interfaces:
- name: Hello
properties:
- { name: last, type: Message }
operations:
- name: say
params:
- { name: msg, type: Message }
- { name: when, type: When }
return:
type: int
signals:
- name: justSaid
params:
- { name: msg, type: Message }
enums:
- name: When
members:
- { name: Now, value: 0 }
- { name: Soon, value: 1 }
- { name: Never, value: 2 }
structs:
- name: Message
fields:
- { name: content, type: string }
The following file structure is generated:
📂ApiGear/Source
┣ 📂ApiGearMQTT
┃ ┣ 📂Private
┃ ┃ ┣ 📜apigearmqtt.cpp
┃ ┃ ┣ 📜ApiGearMQTTClient.cpp
┃ ┃ ┗ 📜ApiGearMQTTHost.cpp
┃ ┣ 📂Public
┃ ┃ ┣ 📜apigearmqtt.h
┃ ┃ ┣ 📜ApiGearMQTTClient.h
┃ ┃ ┣ 📜ApiGearMQTTHost.h
┃ ┃ ┣ 📜ApiGearMQTTTypes.h
┃ ┃ ┣ 📜IApiGearMqttClient.h
┃ ┃ ┗ 📜MqttLoopbackClient.h
┃ ┗ 📜apigearmqtt.Build.cs
┣ 📂ApiGearMQTTPaho
┃ ┣ 📂Private
┃ ┃ ┣ 📜ApiGearMQTTPaho.cpp
┃ ┃ ┗ 📜PahoMqttClient.cpp
┃ ┣ 📂Public
┃ ┃ ┣ 📜ApiGearMQTTPaho.h
┃ ┃ ┗ 📜PahoMqttClient.h
┃ ┗ 📜ApiGearMQTTPaho.Build.cs
┗ 📂ThirdParty/PahoMQTTLibrary
📂IoWorld/Source/IoWorldMQTT
┣ 📂Private
┃ ┗ 📂Generated
┃ ┣ 📜IoWorldMQTT.cpp
┃ ┗ 📂MQTT
┃ ┣ 📜IoWorldHelloMQTTClient.cpp
┃ ┗ 📜IoWorldHelloMQTTAdapter.cpp
┣ 📂Public
┃ ┗ 📂IoWorld
┃ ┣ 📜IoWorldMQTT.h
┃ ┗ 📂Generated/MQTT
┃ ┣ 📜IoWorldHelloMQTTClient.h
┃ ┗ 📜IoWorldHelloMQTTAdapter.h
┗ 📜IoWorldMQTT.Build.cs
ApiGear MQTT Network Layer
The MQTT support is split into two infrastructure modules in the ApiGear plugin:
ApiGearMQTT (transport-agnostic)
ApiGearMQTT defines the abstract transport layer used by every generated MQTT client and adapter. It does not depend on any specific MQTT library:
-
IApiGearMqttClient— the abstract seam. Any MQTT library (Paho, Mqttify, MQTTCore, a mock) can be adapted by implementing this interface:class APIGEARMQTT_API IApiGearMqttClient{public:virtual void Connect(const FString& URL) = 0;virtual void Disconnect() = 0;virtual bool IsConnected() const = 0;virtual FString GetClientId() const = 0;// FMessageCallback receives an FReceivedMessage carrying the// payload plus the MQTT 5 CorrelationData and ResponseTopic// properties (empty for MQTT 3.1.1 backends).virtual void Subscribe(const FString& Topic, FMessageCallback OnMessage,FSubscribeAckCallback OnSubscribed = nullptr) = 0;virtual void Unsubscribe(const TArray<FString>& Topics) = 0;virtual void Publish(const FString& Topic, const TArray<uint8>& Payload,const FPublishOptions& Options) = 0;virtual FOnConnectionChanged& OnConnectionChanged() = 0;}; -
UApiGearMQTTClient— aUAbstractApiGearConnectionconsumed by generated per-interface clients. Holds the broker URL, dispatches subscribe/publish calls to the underlyingIApiGearMqttClient, and tracks pending operation responses. -
UApiGearMQTTHost— a plainUObject(not a subsystem) that adapters use to publish property changes, signals, and operation replies. Multiple hosts can be instantiated to serve different scopes if needed. -
MqttLoopbackClient— an in-processIApiGearMqttClientplus broker for unit testing. Routes publishes directly to subscribers without touching the network.
The UApiGearMQTTClient constant ApiGearMQTTProtocolIdentifier is "mqtt". Generated clients verify this when binding to a connection from the UApiGearConnectionsStore.
This is the Strategy Pattern. The generated code calls into IApiGearMqttClient, and the concrete strategy is chosen at startup based on which backend module is loaded.
ApiGearMQTTPaho (production backend)
ApiGearMQTTPaho provides a production-ready IApiGearMqttClient implementation built on the Eclipse Paho C asynchronous client (MQTTAsync_* API):
-
FPahoMqttClientimplementsIApiGearMqttClientagainstMQTTAsynchandles. -
The module's
StartupModuleregisters a factory under the"mqtt"protocol identifier:void FApiGearMQTTPahoModule::StartupModule(){UApiGearConnectionsStore::RegisterConnectionFactory(ApiGearMQTTProtocolIdentifier, &CreatePahoMQTTConnection);} -
The factory wires a fresh
FPahoMqttClientinto a newUApiGearMQTTClientwhenever the connection store creates an MQTT connection.
The ThirdParty/PahoMQTTLibrary module bootstraps Paho on first build:
- Clones
https://github.com/eclipse-paho/paho.mqtt.c.gitat tagv1.3.14(depth 1, ~2 MB). - Configures and builds the static
paho-mqtt3aslibrary via CMake withPAHO_WITH_SSL=ON. - Links against the OpenSSL bundled with Unreal Engine (auto-detected under
Engine/Source/ThirdParty/OpenSSL).
Build prerequisites: git and cmake must be installed and on PATH. The first build of any MQTT-enabled plugin downloads and compiles Paho; subsequent builds reuse the cached static library.
TLS is supported out of the box. URLs starting with mqtts:// or ssl:// trigger SSL configuration in the Paho backend. For broker authentication, extend FPahoMqttClient::Connect or pre-configure your broker accordingly.
MQTT Client
The UIoWorldHelloMQTTClient class implements IIoWorldHelloInterface (via UAbstractIoWorldHello) and forwards calls to a remote service over MQTT:
UCLASS(NotBlueprintable, BlueprintType)
class IOWORLDMQTT_API UIoWorldHelloMQTTClient : public UAbstractIoWorldHello
{
// properties
FIoWorldMessage GetLast() const override;
void SetLast(const FIoWorldMessage& Last) override;
// operations
int32 Say(const FIoWorldMessage& Msg, EIoWorldWhen When) override;
TFuture<int32> SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When) override;
UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello")
void UseConnection(TScriptInterface<class IApiGearConnection> InConnection);
};
How it works
- Subscribes to topics: On connect, the client subscribes to property, signal, and reply topics for its interface. Subscriptions are tracked so re-connection re-subscribes automatically.
- Caches properties: Property values broadcast on
prop/*topics (retained) update the local cache and fire publisher delegates. - Publishes set requests: Setters publish to
set/*topics; the client also caches the last sent value to suppress redundant publishes. - Invokes operations with MQTT 5 correlation properties: Each operation call generates a
FGuid, attaches it as MQTT 5CorrelationData(and the per-client reply topic asResponseTopic) on the PUBLISH packet, and registers aTPromisekeyed by that GUID. The matching reply — routed via the broker to the client's response topic withCorrelationDataechoed — resolves the promise. - Reports subscription readiness: After all topic subscriptions are acknowledged,
_SubscriptionStatusChangedfires withtrue. Until then, the client is connected to the broker but not yet ready to use.
The client acts as a Remote Proxy - it implements the same interface as the backend service but exchanges payloads via the broker. Properties are cached locally for fast reads, and operation replies are correlated by GUID.
Properties
Property getters return the locally cached value synchronized from the broker. Setters publish a JSON-encoded payload to the set/* topic:
// Getter returns the locally cached value (last received on prop/last)
FIoWorldMessage UIoWorldHelloMQTTClient::GetLast() const
{
return Last;
}
// Setter publishes to "io.world/Hello/set/last"
void UIoWorldHelloMQTTClient::SetLast(const FIoWorldMessage& InLast);
The setter skips the publish if the new value equals either the current cached value or the last sent value, reducing broker traffic when the application sets properties frequently.
Because property notifications are published as retained messages, a newly-subscribing client receives the current property values immediately upon subscription - no separate handshake is required.
Operations
Operations are routed through the broker. Choose between synchronous (blocking) and asynchronous (non-blocking) variants.
Synchronous (Blocking)
int32 UIoWorldHelloMQTTClient::Say(const FIoWorldMessage& Msg, EIoWorldWhen When);
Synchronous calls block the calling thread until the broker delivers the reply. On the game thread, this can cause frame hitches or freezes depending on broker latency. Prefer async variants for network operations.
Asynchronous (Non-Blocking)
Returns a TFuture immediately while the operation completes:
TFuture<int32> UIoWorldHelloMQTTClient::SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When);
Usage examples:
// Chain a callback (recommended for UI updates)
TFuture<int32> Future = HelloClient->SayAsync(Msg, EIoWorldWhen::IWW_Now);
Future.Next([this](const int32& Result) {
AsyncTask(ENamedThreads::GameThread, [this, Result]() {
UpdateUI(Result);
});
});
// Fire and forget
HelloClient->SayAsync(Msg, EIoWorldWhen::IWW_Now);
Internally, SayAsync registers a response handler keyed by a freshly-generated FGuid, then publishes the args-only payload [args...] to io.world/Hello/rpc/say with two MQTT 5 properties: CorrelationData set to the GUID bytes and ResponseTopic set to io.world/Hello/rpc/say/{clientId}/result. The service echoes CorrelationData on its reply, which lands on the response topic; the client matches the GUID and resolves the promise.
Signals
Signals received from the broker are decoded and broadcast to local subscribers via the publisher:
// Subscribe to signals using the Publisher
Hello->_GetPublisher()->OnJustSaidSignalBP.AddDynamic(this, &UMyClass::OnJustSaid);
Signal payloads are dispatched on the game thread via AsyncTask(ENamedThreads::GameThread, ...), so subscribers do not need to marshal manually.
Subscription Status
The MQTT client distinguishes broker connectivity from interface readiness. A client may be connected to the broker but still mid-subscription. Use the subscription status delegate:
// Check if currently subscribed (all topics acknowledged)
if (HelloClient->_IsSubscribed())
{
// Client is ready to use
}
// React to subscription changes
HelloClient->_SubscriptionStatusChanged.AddLambda([](bool bSubscribed) {
UE_LOG(LogTemp, Log, TEXT("Subscription: %s"), bSubscribed ? TEXT("ready") : TEXT("not ready"));
});
Using the MQTT Client
Setup Connection
Create an MQTT connection with MQTTFactory::Create, inject a concrete IApiGearMqttClient (Paho in production), call Configure with the broker URL, and register the connection with the connections store:
#include "ApiGearConnectionsStore.h"
#include "ApiGearMQTTClient.h"
#include "PahoMqttClient.h"
#include "IoWorld/Generated/MQTT/IoWorldHelloMQTTClient.h"
// 1. Create the connection (UApiGearMQTTClient implements IApiGearConnection)
TScriptInterface<IApiGearConnection> Connection =
MQTTFactory::Create(GetTransientPackage(), TEXT("MyMQTTConnection"));
// 2. Inject the Paho backend (use mqtts:// or ssl:// in the URL for TLS)
UApiGearMQTTClient* MQTT = Cast<UApiGearMQTTClient>(Connection.GetObject());
MQTT->SetMqttImplementation(MakeShared<FPahoMqttClient>());
MQTT->Configure(TEXT("tcp://localhost:1883"), /*bAutoReconnect*/ true);
// 3. Register with the engine-wide connections store so generated clients can find it
UApiGearConnectionsStore* Store = GEngine->GetEngineSubsystem<UApiGearConnectionsStore>();
Store->AddConnection(Connection);
// 4. Bind the per-interface MQTT client (GameInstance subsystem)
UIoWorldHelloMQTTClient* HelloClient = GetGameInstance()->GetSubsystem<UIoWorldHelloMQTTClient>();
HelloClient->UseConnection(Connection);
Connection->Connect();
// 5. Use as IIoWorldHelloInterface once subscriptions are acknowledged
HelloClient->_SubscriptionStatusChanged.AddLambda([HelloClient](bool bReady) {
if (bReady) {
TScriptInterface<IIoWorldHelloInterface> Hello = HelloClient;
FIoWorldMessage Msg;
Hello->Say(Msg, EIoWorldWhen::IWW_Now);
}
});
UApiGearConnectionsStore exposes GetConnection, AddConnection, and RegisterConnectionFactory — there is no combined "get-or-create" helper. Either use MQTTFactory::Create directly (as above) or pre-configure the connection in Project Settings and look it up via Store->GetConnection(Identifier).
Using Project Settings
Configure connections in Project Settings under ApiGear:
- Open Project Settings > Plugins > ApiGear
- Add a new connection and pick the
mqttprotocol from the dropdown - Set the broker URL (e.g.,
tcp://localhost:1883ormqtts://broker.example.com:8883) - Optionally enable auto-connect
The Project Settings dropdown lists every protocol that has registered a factory. Loading ApiGearMQTTPaho registers the "mqtt" entry.
The generated client picks up the configured connection automatically via the module setting MQTTConnectionIdentifier:
UIoWorldSettings* settings = GetMutableDefault<UIoWorldSettings>();
TScriptInterface<IApiGearConnection> MQTTConnection =
AGCM->GetConnection(settings->MQTTConnectionIdentifier);
MQTT Adapter (Service)
The UIoWorldHelloMQTTAdapter wraps a local IIoWorldHelloInterface implementation and exposes it to MQTT subscribers through a UApiGearMQTTHost:
UCLASS(BlueprintType)
class IOWORLDMQTT_API UIoWorldHelloMQTTAdapter : public UGameInstanceSubsystem,
public IIoWorldHelloSubscriberInterface
{
UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello")
void setBackendService(TScriptInterface<IIoWorldHelloInterface> InService);
UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello")
void setMQTTHost(TSoftObjectPtr<UApiGearMQTTHost> InHost);
};
How it works
- Wraps local implementation: Subscribes to the backend's publisher to forward signals and property changes.
- Subscribes to inbound topics: When the host connects, the adapter subscribes to its
set/*andrpc/*topics. - Handles set requests: Decodes the JSON payload and forwards the value to the backend.
- Handles operation invocations: Reads
CorrelationDataandResponseTopicfrom the inbound MQTT 5 properties, decodes the args-only payload, invokes the backend, and publishes the result to the caller'sResponseTopicwithCorrelationDataechoed back. - Broadcasts updates: When the backend's property or signal fires, the adapter publishes to
prop/*(retained) orsig/*. - Replays state on reconnect: When the host reconnects, the adapter republishes current property values so retained-message storage stays consistent.
The adapter uses the Adapter Pattern - it wraps any IIoWorldHelloInterface implementation and exposes it via the broker. Multiple heterogeneous subscribers (Unreal, Python, web, embedded) can interact with it simultaneously.
Properties
When a subscriber publishes to set/last, the adapter decodes the JSON and forwards the value to the backend. The backend's property change notification is then broadcast on prop/last with retain=true:
void UIoWorldHelloMQTTAdapter::OnLastChanged(const FIoWorldMessage& InLast)
{
if (!Host.IsValid() || !bIsConnected) { return; }
const FString Topic = TEXT("io.world/Hello/prop/last");
Host.Get()->notifyPropertyChange(Topic, UTF8_TO_TCHAR(nlohmann::json(InLast).dump().c_str()));
}
Operations
Remote operation calls carry their correlation in the MQTT 5 CorrelationData property and tell the service where to publish the reply via ResponseTopic. The adapter just echoes those properties back, so concurrent invocations from different clients never collide:
// InMsg.CorrelationData and InMsg.ResponseTopic are MQTT 5 properties
// from the inbound PUBLISH packet.
int32 Result = BackendService->Say(Msg, When);
const nlohmann::json ResponsePayload = Result;
Host.Get()->notifyInvokeResponseWithCorrelation(
InMsg.ResponseTopic,
UTF8_TO_TCHAR(ResponsePayload.dump().c_str()),
InMsg.CorrelationData);
Signals
The adapter subscribes to the backend implementation's publisher. When the implementation broadcasts a signal, the adapter forwards it to sig/*:
LocalImpl->_GetPublisher()->BroadcastJustSaidSignal(Msg);
Using the MQTT Adapter
#include "ApiGearMQTTHost.h"
#include "IoWorld/Generated/MQTT/IoWorldHelloMQTTAdapter.h"
#include "IoWorld/Implementation/IoWorldHello.h"
// Get your local implementation (GameInstance subsystem)
UIoWorldHelloImplementation* LocalHello =
GetGameInstance()->GetSubsystem<UIoWorldHelloImplementation>();
// Create and start an MQTT host
UApiGearMQTTHost* Host = NewObject<UApiGearMQTTHost>(GetTransientPackage());
// Production: wire in the Paho backend
Host->SetMqttImplementation(MakeShared<FPahoMqttClient>());
Host->Start(TEXT("tcp://localhost:1883"));
// Get the adapter (GameInstance subsystem)
UIoWorldHelloMQTTAdapter* Adapter =
GetGameInstance()->GetSubsystem<UIoWorldHelloMQTTAdapter>();
Adapter->setBackendService(LocalHello);
Adapter->setMQTTHost(Host);
// Adapter publishes property/signal changes; subscribers consume them via the broker
The adapter is a UGameInstanceSubsystem, but the host is a plain UObject. This lets you instantiate multiple hosts (for example, to publish to different brokers or topic prefixes) while keeping a single adapter per interface.
Communication Flow
The following diagram traces a complete interaction between a client and an adapter through the broker:
Testing with MQTT
The mqtt_tests feature generates test fixtures that exercise the full client/adapter pair without needing a real broker:
📂IoWorld/Source/IoWorldMQTT/Private/Tests
┣ 📜IoWorldHelloMQTT.spec.cpp
┣ 📜IoWorldHelloMQTTFixture.h
┗ 📜IoWorldHelloMQTTFixture.cpp
The fixture wires up an in-process broker (FMqttLoopbackBroker) and two FMqttLoopbackClient instances - one for the host and one for the client - so the test runs entirely in memory:
// from IoWorldHelloMQTTFixture.cpp
auto Broker = MakeShared<FMqttLoopbackBroker>();
auto ClientLoopback = MakeShared<FMqttLoopbackClient>(Broker, TEXT("loopback-client"));
auto HostLoopback = MakeShared<FMqttLoopbackClient>(Broker, TEXT("loopback-host"));
Host = NewObject<UApiGearMQTTHost>(GetTransientPackage());
Host->SetMqttImplementation(HostLoopback);
GetAdapter()->setBackendService(service);
GetAdapter()->setMQTTHost(Host.Get());
Host->Start(TEXT("loopback://test"));
Connection = MQTTFactory::Create(GetTransientPackage(), TEXT("MQTTTestingConnection"));
auto* MQTTConnection = Cast<UApiGearMQTTClient>(Connection.GetObject());
MQTTConnection->SetMqttImplementation(ClientLoopback);
MQTTConnection->Configure(TEXT("loopback://test"), false);
These tests verify:
- Subscription handshake and
_IsSubscribed()readiness signaling - Property synchronization via retained messages
- Operation invocation and per-client reply routing
- Signal propagation onto the game thread
Running Tests
UnrealEditor-Cmd.exe YourProject.uproject -ExecCmds="Automation RunTests IoWorld.Hello.MQTT"
The loopback transport is also useful in your own tests when you need deterministic MQTT semantics without provisioning a broker. Construct an FMqttLoopbackBroker, share it across as many FMqttLoopbackClients as you need, and inject them via SetMqttImplementation on the host and connection.
Best Practices
Broker Selection
- For local development, Mosquitto is the simplest broker to run.
- For production, choose a broker that supports your scale and reliability requirements (EMQX, HiveMQ, AWS IoT Core, Azure IoT Hub).
- Configure broker authentication, ACLs, and TLS appropriately - the generated code defers credential handling to your
IApiGearMqttClientimplementation.
Topic Discipline
- The generated topic structure (
{module}/{Interface}/{action}/{member}) is fixed by the template. Do not publish to these topics from non-generated code unless you fully understand the payload format. - If multiple Unreal instances run the same plugin, they all subscribe to and publish on the same topics. Coordinate which instance acts as the adapter.
Connection Lifecycle
- Wait for
_SubscriptionStatusChanged(true)before invoking operations - publishing before subscriptions are acknowledged risks losing replies. - Re-subscription on reconnect is automatic. Pending operation promises are cancelled on
Deinitializeto avoid use-after-free in test teardown. - Adapters re-publish their current property values when the host reconnects, keeping retained-message storage consistent.
Performance Considerations
- MQTT publishes are network calls - avoid invoking operations every frame.
- Property setters automatically suppress duplicate publishes; rely on this rather than guarding manually.
- Use QoS 1 (the default) unless you have a specific reason to relax delivery guarantees.
- The generated MQTT code includes CPU profiler instrumentation (
TRACE_CPUPROFILER_EVENT_SCOPE_STR) for performance tracing in Unreal Insights.