S9SourceNine Labs / CMAPI Docs 1.0
CMAPI / Getting started

Create your first mod

Build, install, and test a CMAPI 1.0 mod.

View source ↗
CMAPI 1.0.0Standalone runtimeWindows x64Tested with Planet Crafter 2.008

Create your first CMAPI mod

This guide targets CMAPI 1.0.0 (public API 1.0.0) and the complete version of The Planet Crafter. Test mods against the exact minimum public API version they declare before distributing them.

What you need

  • Visual Studio with the .NET development workload;
  • a .NET class-library project targeting netstandard2.1;
  • CMAPI.API.dll from the game's CMAPI\Runtime folder;
  • CMAPI.API.xml beside that DLL for editor documentation.

An ordinary CMAPI mod references only CMAPI.API.dll. It does not need CMAPI's runtime DLL, Unity, or Assembly-CSharp.dll for the API shown here.

1. Create the project

Create a C# class library and use a project file like this:

XML
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="CMAPI.API">
      <HintPath>path\to\CMAPI.API.dll</HintPath>
      <Private>false</Private>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>
</Project>

Private=false prevents your mod from shipping a second copy of CMAPI's public API. Change the HintPath to the DLL on your own machine.

2. Add a manifest

Create manifest.json in the project directory:

JSON
{
  "Name": "Hello Crafter",
  "Author": "Your Name",
  "Description": "Adds a friendly console greeting command.",
  "Version": "1.0.0",
  "MinimumApiVersion": "1.0.0",
  "UniqueID": "YourName.HelloCrafter",
  "EntryDll": "HelloCrafter.dll",
  "UpdateKeys": [
    "GitHub:YourGitHubName/HelloCrafter"
  ]
}

Use a stable UniqueID that belongs to you. EntryDll must name a DLL inside the mod's own folder. Versions use semantic versioning: major.minor.patch, optionally followed by a prerelease such as 1.0.0-beta.1.

MinimumApiVersion prevents the mod from running against an older CMAPI API it was not built to support. UpdateKeys is optional; omit it or use an empty array until the mod has a real release feed. GitHub keys check the repository's latest stable release tag, which should be 1.0.0 or v1.0.0. See MANIFEST.md for every field and provider.

3. Add the mod entry point

Your assembly must contain exactly one public, non-abstract class derived from CMAPI.Mod. It needs a parameterless constructor; the default constructor is fine.

CSHARP
using CMAPI;

namespace HelloCrafter
{
    public sealed class HelloMod : Mod
    {
        public override void Entry(IModHelper helper)
        {
            Monitor.Info("Hello Crafter loaded!");

            helper.ConsoleCommands.Add(
                "hello_crafter",
                "Greets a crafter by name.",
                new ConsoleCommandOptions(
                    "hello_crafter <name>",
                    "Social",
                    "hello"
                ),
                OnHelloCommand
            );
        }

        private void OnHelloCommand(IConsoleCommandContext context)
        {
            if (context.Arguments.Count != 1)
            {
                context.WriteUsage();
                return;
            }

            context.WriteLine($"Hello, {context.Arguments[0]}!");
        }
    }
}

Command names are global and case-insensitive. They must start with a lower-case letter and contain only lower-case letters, numbers, underscores, or hyphens. Descriptions should say what the command does in one sentence. In usage text, use <value> for required arguments and [value] for optional arguments. ConsoleCommandOptions adds a player-facing category and optional aliases. Use the older usage-string overload if the command needs neither; CMAPI puts it in the default Mods category.

Command handlers run on the game's main thread. Read game state or make quick changes there, but move slow work elsewhere so the game does not freeze. CMAPI logs an exception from a handler and keeps the console working.

4. Build and install

Build the project, then make this folder:

<GameRoot>\CMAPI\Mods\HelloCrafter\
    manifest.json
    HelloCrafter.dll

Put the mod only in its own CMAPI\Mods subfolder. Start the game through CMAPI.exe; startup should show the mod loading successfully.

Try these commands in the CMAPI console:

mods
modinfo YourName.HelloCrafter
updates
commands social
help hello_crafter
hello_crafter Alex
hello Alex
hello_crafter "Alex the Crafter"

Quoted text is one argument, so the final command prints Hello, Alex the Crafter!.

Player example

The local player is unavailable at the main menu. Check for null, or wait for LocalPlayerStarted:

CSHARP
helper.Events.Player.LocalPlayerStarted += player =>
{
    Monitor.Info($"{player.Name} entered at {player.Position}.");
};

IPlayer.ClientId identifies a connection only for the current session. Player position and vitals are live while connected; stopped-event objects retain the last valid snapshot with IsConnected == false.

Move the local host safely

Mods can use CMAPI's Unity-free same-planet placement service:

CSHARP
helper.Events.Player.LocalPlayerStarted += async player =>
{
    if (!player.IsHost)
        return;

    PlayerPlacementResult result = await helper.PlayerPlacement.TeleportAsync(
        new WorldPosition(559f, 2f, 600f)
    );

    if (!result.Succeeded)
        Monitor.Warning($"Placement: {result.Status} — {result.Message}");
};

The service validates finite bounded coordinates, stays on the current planet, uses the native main-thread placement path, and records a return point scoped to your mod. Call await helper.PlayerPlacement.ReturnAsync() for recovery. See PLAYER-PLACEMENT-API.md before shipping it.

Item, Terra Token, and notification examples

CMAPI exposes the item catalog and shared Terra Token balance without requiring a game-assembly reference:

CSHARP
helper.Events.Player.LocalPlayerStarted += player =>
{
    Monitor.Info($"Shared Terra Tokens: {helper.TerraTokens.Count}");

    foreach (IItemDefinition item in helper.Items.FindItems("uranium"))
        Monitor.Info($"{item.Id}: {item.DisplayName}");

    helper.Notifications.Show("Hello from the native Planet Crafter HUD!");
};

AllItems and FindItems return snapshots. They may be empty at the main menu; query again after LocalPlayerStarted. IItemDefinition.Id is the stable game ID, while DisplayName follows the player's selected language.

CMAPI 0.7.0 introduced host-safe asynchronous gameplay mutations, retained in 1.0.0:

CSHARP
helper.Events.Player.LocalPlayerStarted += async _ =>
{
    GameplayActionResult result = await helper.Items
        .AddToLocalBackpackAsync("Iron", 2);

    if (!result.Succeeded)
        Monitor.Warning($"Item grant failed: {result.Status} — {result.Message}");
};

Mutations may be requested from any thread; CMAPI performs the game interaction on the main thread. They return structured failures for normal problems such as being a client, an unavailable world, a full backpack, an unknown item ID, or a timeout. Successful mutations automatically show the matching native in-game notification.

Weather and environment example

CMAPI 0.9.0 exposes current-planet environment data without Unity references:

CSHARP
helper.Events.Player.LocalPlayerStarted += async player =>
{
    EnvironmentSnapshot environment = helper.Environment.GetSnapshot();
    WorldStateSnapshot world = helper.WorldState.GetSnapshot();

    Monitor.Info(
        $"{helper.Planets.CurrentPlanet?.Id}: " +
        $"{environment.DayNightPhase}, " +
        $"{world.CurrentStage?.Id ?? "unknown stage"}"
    );

    IWeatherEventDefinition? rain = helper.Weather
        .FindEvents("rain")
        .FirstOrDefault();

    if (player.IsHost && rain != null && !helper.Weather.Status.IsActive)
    {
        EnvironmentActionResult result =
            await helper.Weather.StartAsync(rain.Id);

        if (!result.Succeeded)
            Monitor.Warning($"Weather: {result.Status} — {result.Message}");
    }
};

Add using System.Linq; for FirstOrDefault. Read catalogs again after planet travel and always check availability. Weather/daylight mutations are host-only and return structured normal failures. See ENVIRONMENT-API.md before publishing an environment mod.

Recipe, unlock, inventory, and world-event example

Use WorldEntered when code needs both a local player and a fully loaded planet, then read fresh snapshots:

CSHARP
helper.Events.World.WorldEntered += world =>
{
    Monitor.Info($"Ready on {world.PlanetId}.");
    Monitor.Info($"Recipes: {helper.Recipes.AllRecipes.Count}");
    Monitor.Info($"Unlock entries: {helper.Unlocks.AllUnlocks.Count}");

    PlayerInventorySnapshot backpack = helper.PlayerInventory.Backpack;
    Monitor.Info($"Backpack: {backpack.OccupiedSlots}/{backpack.Capacity}");
};

helper.Events.World.Saved += world =>
    Monitor.Info($"Save completed: {world.SaveName ?? "unknown"}.");

helper.PlayerInventory.Changed += change =>
    Monitor.Info($"{change.Kind}: {change.ItemId}, added={change.WasAdded}");

See CONTENT-AND-LIFECYCLE-API.md before caching content or interpreting live unlock availability.

Common problems

Message or symptom What to check
no manifest.json found The file must be named exactly manifest.json and sit in the mod folder.
missing its entry DLL EntryDll must exactly match the built DLL's filename.
doesn't contain a public CMAPI Mod class Make the entry class public, concrete, parameterless, and derived from Mod.
contains more than one CMAPI Mod class Keep exactly one concrete public Mod subclass in the assembly.
already registered by ... Choose a different globally unique command name.
invalid manifest with a field list Fix every listed manifest issue; CMAPI reports all detected problems together.
requires CMAPI ... or later Update CMAPI or lower the requirement only if the mod truly supports the older API.
API types are missing or incompatible Reference the CMAPI.API.dll installed with the same CMAPI runtime version.
No IntelliSense descriptions appear Put CMAPI.API.xml beside the referenced CMAPI.API.dll, then reload the project.
The mod loads twice Remove old or duplicate copies from other folders under CMAPI\Mods.

For a complete runtime verification pass, follow TESTING.md. The CMAPI.TestMod project is also a working example of player events, player lookup, native notifications, structured gameplay and environment results, weather/world/planet snapshots, catalog categories, aliases, usage validation, and quoted command arguments.