Skip to main content

Creating Plugins

The Plugin API is included as part of the MSFS SDK library (@microsoft/msfs-sdk). In order to use the API, the first step is to make sure your plugin imports @microsoft/msfs-sdk. Once the library is imported, you can start using the Plugin API classes.

Setting Up Your Plugin Class

Plugins are implemented as classes that extend the abstract class AvionicsPlugin. Individual instruments have the option to define their own plugin interfaces, so before doing anything else, you should take a minute to look up the specific interface required by the instrument for which you are developing your plugin. Once you have done that, simply create a class that both inherits from AvionicsPlugin and implements the instrument-specific plugin interface (if one exists).

Below is an example of a very basic plugin. To keep things simple, the example assumes that there is no instrument-specific plugin interface, so we just have to extend AvionicsPlugin.

import { AvionicsPlugin } from '@microsoft/msfs-sdk';

// Don't worry about the type parameter for now; we'll get to that later.
class MyPlugin extends AvionicsPlugin<void> {
public onInstalled(): void {
// ...
}
}

AvionicsPlugin only requires one method to be implemented: onInstalled(). This is a callback method that is called when the plugin is first loaded by the instrument. It is a good place to run initialization code.

Once you have defined your plugin class, the next step is to register it with the plugin system. You can do this using the global function registerPlugin():

import { AvionicsPlugin, registerPlugin } from '@microsoft/msfs-sdk';

// Don't worry about the type parameter for now; we'll get to that later.
class MyPlugin extends AvionicsPlugin<void> {
public onInstalled(): void {
// ...
}
}

registerPlugin(MyPlugin);

Loading Your Plugin

Next, you need to build your plugin code to a Javascript (.js) file and place the file in a directory that will be loaded by the sim's virtual file system.

If you are creating an airplane-specific plugin, then a good location would be under the airplane's panel/Instruments/ directory. Another possible location is under the global html_ui/ directory; however care must be used when adding files here to avoid conflicts with files from the base sim or other third-party packages.

After you have chosen a suitable location for your plugin file, you need to tell the sim where your plugin file is located so it can be loaded. The method to do this depends on if your plugin is a global plugin or an airplane plugin.

Loading Global Plugins

Global plugins are loaded using XML files placed in the following directory:

html_ui/Plugins
caution

While you are free to choose any name for your global plugin XML files, please take care to choose names that are not likely to conflict with files used by other plugins. Files with the same names will overwrite one another and will cause issues if the overwriting is not intended.

Global plugin XML files should contain the <Plugins> tag at the root. The <Plugins> tag should in turn contain one or more <Plugin> tags. Each <Plugin> tag loads one plugin .js file:

<Plugins>

<Plugin target="MyInstrument">
coui://html_ui/Path/To/My/Plugin/MyPlugin.js
</Plugin>

<Plugin target="MyOtherInstrument">
coui://html_ui/Path/To/My/Plugin/MyOtherPlugin.js
</Plugin>

</Plugins>

The target attribute of each <Plugin> tag defines the target of all plugins loaded from the tag's .js file. The target is used by instruments to determine whether to use a particular global plugin. This allows instruments to avoid using plugins that were not meant to be applied to them. Each instrument that supports plugins is free to filter global plugins based on whatever arbitrary criteria it chooses, so please always confirm that your plugin's declared target matches what is expected by the instrument. If you want your plugin to apply to multiple instruments that expect different target strings, create separate <Plugin> tags with the appropriate target strings for the different instruments.

Loading Airplane Plugins

Airplane plugins are loaded using the airplane's panel.xml file. Find (or create) the <Instrument> tag associated with your plugin's parent instrument, then add a <Plugin> tag as a child with the content of the tag specifying the full absolute path to the plugin file.

For example, the following panel.xml will cause the instrument with ID MyInstrument to load MyPlugin.js.

<PlaneHTMLConfig>
<Instrument>
<Name>MyInstrument</Name>
<Plugin>
coui://SimObjects/Airplanes/Company_MyAirplane/panel/Instruments/MyPlugin.js
</Plugin>
</Instrument>
</PlaneHTMLConfig>

If you wish to load multiple plugins for a single instrument, you may either build them all to a single .js file and load that file or build each plugin to its own .js file and specify multiple files to be loaded in panel.xml. When loading more than one .js file, create a separate <Plugin> tag for each file to be loaded.

info

If an airplane has multiple instances of an instrument that all need to load the same plugin(s), the plugin file(s) should be specified for all instances of the instrument, each of which will have its own <Instrument> tag in panel.xml.

Getting Data from the Instrument

Instruments may choose pass data to plugins via a binder, which is an object that is passed to plugins via their constructors. Instruments that use plugin binders should also declare an interface for such binders so that plugins are aware of what is contained in the binder. Once the binder interface is known, it can be used as the type parameter on AvionicsPlugin to properly expose the type of the binder class property:

// ------------------------------------
// Declared by the instrument somewhere
export interface PluginBinder {
readonly bus: EventBus;
}
// ------------------------------------

class MyPlugin extends AvionicsPlugin<PluginBinder> {
public onInstalled(): void {
// 'this.binder' now has the type 'PluginBinder'
this.binder.bus
.getSubscriber<ClockEvents>()
.sub('realTime')
.handle(time => { console.log(`The time is now ${new Date(time).toTimeString()}`); });
}
}

Overriding Rendered Components

Normally, instruments get to decide how plugin-specific functionality is integrated. For example, an instrument may allow plugins to render or replace certain display components through the use of specific methods declared by its plugin interface:

export interface InstrumentPlugin extends AvionicsPlugin<void> {
/**
* Renders a component.
* @returns The rendered component, or null if this plugin does not support rendering the component.
*/
renderComponent(): VNode | null;
}

This allows instruments a certain degree of control over what parts of themselves plugins can and cannot override. However, sometimes you may find yourself in the position of needing your plugin to override a display component in a manner that the instrument plugin interface does not explicitly support. The Plugin API allows you to accomplish this without resorting to forking the instrument code.

AvionicsPlugin supports an optional callback method onComponentCreating(). If this method is defined in a subclass, then it will be called whenever any instance of DisplayComponent is about to be created on the plugin's parent instrument. It is passed the constructor of the component and the props that are to be used to create the component. If onComponentCreating() returns undefined, then the original component will be created as usual. However, if onComponentCreating() returns its own instance of DisplayComponent, the returned instance will be used in place of the original component. Effectively, you can use onComponentCreating() to intercept the creation of certain components and silently replace them with your own versions.

For example, the following code will cause all instances of MyComponent to be replaced with MyPluginComponent. Everywhere MyComponent would have been rendered, there will instead be a <div> with the text "Hello, world!".

class MyPluginComponent extends DisplayComponent<ComponentProps> {
public render(): VNode {
return (
<div>Hello, world!</div>
);
}
}

class MyPlugin extends AvionicsPlugin<void> {
public onInstalled(): void {
// ...
}

public onComponentCreating = (ctor: DisplayComponentFactory<any>, props: any): DisplayComponent<any> | undefined => {
if (ctor.name === 'MyComponent') {
return new MyPluginComponent(props);
}

return undefined;
};
}
danger

When replacing components with onComponentCreating(), it is imperative that the replacement component implement the same interface as the original. Otherwise, you may cause runtime errors to be thrown when instrument code attempts to access properties on the replacement that do not exist.

Component Hooks

If your plugin needs to know when certain components are created or rendered in the instrument, you can subscribe to those events by defining the optional onComponentCreated() and onComponentRendered() methods in your plugin class. The former is called immediately after any component is created in the instrument, and the latter is called immediately after any component is rendered.