EntityComposer can be extended through a Lua based extension system. On startup all extensions are loaded from the following directory:

  • Windows: C:\Users\<username>\AppData\Local\InfectedBytes\EntityComposer\extensions
  • Linux: "~/.local/share/InfectedBytes/EntityComposer/extensions"

Each subdirectory corresponds to one extension, which must provide a manifest file (manifest.json) and an entry point (main.lua).

A manifest can look like this:

  "id": "com.infectedbytes.my-awesome-extension",
  "version": "0.1.0",
  "composerVersion": ">=0.1.0",
  "name": "My Awesome Extension",
  "description": "Some simple test extension",
  "dependencies": {
    "com.infectedbytes.some-other-extension": ">=0.1"
  "exports": ["myPublicAPI"]
Entry Type Meaning
id string Unique id for the plugin, must be the same as the directory name. It is good practice to follow the java package naming convention.
version string Version of this extension (major.minor.patch).
composerVersion string Required version of the EntityComposer. This is a simple version expression, for example: >=0.1.0
name string Name of this extension.
description string Description of this extension.
dependencies object Contains all extensions this extension requires to run. If any of the dependencies is missing or only present in an unsupported version, the loading fails.
exports array List of strings containing the files (without .lua) that can be required from other extensions.


The normal lua require function has been replaced by modified version. Just as the regular function it will ensure that a file is never loaded twice, instead the result from the first loading is returned.

  • local xyz = require "myOtherFile" - simply loads myOtherFile.lua of the extension
  • local xyz = require "some-other-plugin-id/exportedFile - loads exportedFile.lua of the some-other-plugin-id extension, but only if the file is exported via the manifest.


The registerExporter function is used to register a new exporter.

A very simple exporter looks like this:

local myExporter = class("MyExporter", Exporter) -- create a new class inheriting from the Exporter class
myExporter.static.isFilePath = true -- tell the UI that it should provide a file chooser
-- define some settings that will be shown in the UI
myExporter.static.declaredSettings = {
  setting("prettyPrint", "bool", true), -- define a boolean setting called "prettyPrint" that defaults to true
  setting("value", "int", 42), -- define an int setting called "value" that defaults to 42
  setting("test", "option", {"A", "B", "C"}) -- define a dropdown setting called "test" that has three options (A, B, C), defaulting to A

-- tell the system that we want to serialize enums using their fullname:
function myExporter:getEnumSerializationStrategy() return EnumSerialization.TypeDotName end

-- tell the system that we want to serialize entity references using their sequential id
function myExporter:getEntityReferenceKey() return EntityReferenceKey.Id end

-- tell the system that we want to resolve files relative to the export root
function myExporter:getFileResolutionStrategy() return FileResolutionStrategy.ExportRoot end

-- heart of the exporter. This function is called when the actual export happens.
function myExporter:execute()
  local all = self.composition:collectUserEntities() -- get a sequential list of all user defined entities
  local result = json.object() -- create empty json object
  for k,entity in ipairs(all) do -- iterate over all entities
    local e = json.object()
    result[self:getEntityKey(entity)] = e
    e.name = entity.name -- same as e["name"] = entity.name
    e.components = json.object()
    for i,ci in ipairs(entity.components) do
      e.components[ci.component.name] = self:typeValueToJson(ci.component, ci.value)
  local f = self:open()
  f:write(json.serialize(result, self.settings.prettyPrint)) -- serialize json object using the prettyPrint setting

-- register the freshly defined exporter