# Netcode native plugins and platform matrix

> How to read the Netcode SDK catalog: per-platform native binaries, where each artifact goes in a Unity project, ABI selection, signing requirements, and what the channel and status chips mean.

The Netcode SDK catalog in the FirstByte console has two tables. The
**Package catalog** table lists the engine-side packages (install command,
version, channel, per-platform support, status). The **Native plugins** table
lists the binaries those packages load — one row per build target.

This page covers the second table: what each column is telling you, where the
artifact belongs in a Unity project, and the exact guarantee each chip in the
first table carries.

## What ships as a native plugin

Every binding in the catalog is a thin layer over the same C wire core, exposed
through the FFI header (`clutchcall_moqt_ffi.h`) that ships with the
package. The engine package gives you the Unity-facing transport surface; the
native plugin is the QUIC + MoQT implementation underneath it. The KPI strip
shows the core version currently published alongside the packages
(`Wire core · FFI`), and the `Native targets` KPI is just the row count of the
Native plugins table.

The Native plugins table has these columns:

| Column     | What it is                                                                 |
| ---------- | -------------------------------------------------------------------------- |
| `Platform` | The build target the binary is for. One row per target.                    |
| `Artifact` | The file name as published. This is the file you place in the project.     |
| `ABI`      | The architecture(s) the binary contains.                                   |
| `Signing`  | The signing or notarisation requirement that applies on that platform.     |
| `Size`     | The published artifact size, for build-budget planning on mobile targets.  |

The **Native plugins** button in the page header downloads the set. If you
install via UPM, the package already carries the binaries for the platforms its
matrix row marks as supported — you only place artifacts by hand when you pull
them separately, or when you vendor a single target into an existing plugin
folder.

## Where each artifact goes in a Unity project

Unity decides how to import a native binary from its location and from the
import settings on its `.meta` file. Place the file from the `Artifact` column
under `Assets/Plugins/` in the directory for its target, then open the Plugin
Inspector and confirm the platform and CPU selection matches the `ABI` column:

| Platform | Conventional location                                | Notes                                                                                  |
| -------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Win      | `Assets/Plugins/x86_64/`                             | Loaded at runtime by name. The file name must match the name used in `DllImport`.       |
| macOS    | `Assets/Plugins/`                                    | Bundles are directories; keep them intact. The editor loads the same artifact the player does. |
| Linux    | `Assets/Plugins/x86_64/`                             | Loaded at runtime by name.                                                              |
| Android  | `Assets/Plugins/Android/libs/<abi>/`                 | One directory per ABI (`arm64-v8a`, `armeabi-v7a`, …). Gradle packages whatever is present. |
| iOS      | `Assets/Plugins/iOS/`                                | Linked into the Xcode project Unity generates, not loaded at runtime.                   |
| WebGL    | `Assets/Plugins/WebGL/`                              | Linked into the wasm module at build time. WebGL cannot load a shared library at runtime. |

Two rules that cause most of the support traffic:

- **Set the platform on the `.meta`, not just the folder.** A binary left with
  "Any Platform" selected can be included twice, or included on a target it was
  not built for, and the failure surfaces only in the player build.
- **Keep the header next to the binary from the same release.** The
  `clutchcall_moqt_ffi.h` in the package and the binary in the Native
  plugins table are versioned together. See
  [Pinning the FFI core version](#pinning-the-ffi-core-version-alongside-the-package-version).

## Picking the right ABI

The `ABI` column is the authoritative list of architectures inside each
artifact. Match it against the architecture your player is built for, not
against your development machine:

- **Editor vs player.** The editor loads the desktop artifact for the machine
  you are running on. An Apple-silicon editor needs an arm64 slice even when
  you only ship x86_64 players; a single-slice binary will work in one and fail
  in the other.
- **Android.** The ABIs you ship must cover the "Target Architectures" set in
  Player Settings. If Player Settings includes an ABI with no matching `.so`
  under `Assets/Plugins/Android/libs/`, the store build still succeeds and the
  app fails on that device class only.
- **iOS.** Device and simulator are different slices. If the artifact you were
  given does not list a simulator slice, build to a device.
- **WebGL.** There is no ABI choice; the native side is linked in by the
  Emscripten toolchain. The toolchain version has to match the one your Unity
  version ships with.

## What an ABI mismatch looks like at runtime

A mismatch never shows up as a compile error. The symptoms, in order of how
often they are reported:

| Symptom                                                                         | Usual cause                                                                                     |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `DllNotFoundException` naming the plugin, in the editor or a desktop player      | Wrong architecture slice for the host, wrong folder, or the `.meta` excludes the current platform. |
| `EntryPointNotFoundException` on a specific call, everything else working        | Header and binary are from different releases. The symbol exists in the header, not in the loaded library. |
| `java.lang.UnsatisfiedLinkError` at first connect on Android                     | The device ABI has no matching `.so` packaged.                                                   |
| Undefined-symbol link errors in the generated Xcode project                      | iOS artifact missing, or the wrong slice (simulator vs device) is in `Assets/Plugins/iOS/`.      |
| Missing-function errors at wasm link time                                        | WebGL artifact not present, or built against a different Emscripten toolchain.                   |

When you hit the second row, treat it as a version problem rather than a
placement problem: re-pull both the header and the binary from the same catalog
row.

## Signing and notarisation per platform

The `Signing` cell on each Native plugins row records the requirement for that
target. Read the cell as the source of truth for the artifact you downloaded;
what follows is how the requirement is usually satisfied in a Unity pipeline.

- **macOS.** A native plugin inside an app bundle is signed as part of signing
  the app. If you enable the hardened runtime and notarise the build, the plugin
  has to carry your signature too — re-sign the bundle after Unity copies it in,
  then notarise the app as a whole. An unsigned or stale-signed plugin is the
  usual reason a notarised build launches and then fails on first connect.
- **iOS.** Signing is handled by Xcode as part of signing the app. Static
  artifacts inherit the app signature; if you were given a framework instead,
  it must be embedded and signed, not merely linked.
- **Android.** The `.so` files are not signed individually; the APK or AAB
  signature covers them. Do not strip or re-compress them after the Gradle step.
- **Windows.** Authenticode signing of the plugin is separate from signing your
  installer. Sign the plugin if your distribution channel checks per-file
  signatures.
- **Linux.** No platform signing step.
- **WebGL.** Nothing to sign; the code is linked into the wasm module and is
  covered by however you serve and integrity-check your build output.

Whatever the platform, sign **after** Unity has finished writing the player
output. Post-build steps that copy plugins around invalidate signatures made
earlier in the pipeline.

## Release channels and status chips

The two chips in the Package catalog table describe different things and are
counted separately in the KPI strip.

| Chip      | Column    | What it records                                                                                   |
| --------- | --------- | ------------------------------------------------------------------------------------------------- |
| `stable`  | `Channel` | The binding is a GA build. The `Stable channel` KPI counts these rows and labels them "GA bindings". |
| `live`    | `Status`  | The row is currently published and installable. The `SDKs` KPI hint counts these.                  |

What they do not tell you:

- **A channel is not a compatibility contract across versions.** The chip
  describes the release track of the row, not a promise that the next version on
  the same track keeps the same API surface. The number to reason about for
  compatibility is the version pair in the next section.
- **A channel says nothing about the platform cells beside it.** Channel and
  the per-platform matrix are independent columns; a `stable` row can still show
  reduced support for an individual platform.
- **`live` is a catalog state, not a maturity level.** A row can be `live` on a
  non-stable channel. Read both chips before you pin a version into a shipping
  build.

Only the `live` rows get an install card below the table, with the exact install
command and the row's note. Copy the command from the card rather than typing a
package name from the table — the `pkg` line in the table is the package
identifier, not the full command.

## The platform support matrix

The six columns after `Channel` in the Package catalog table are the build
targets: `Win`, `macOS`, `Linux`, `Android`, `iOS`, `WebGL`. Each cell shows
that binding's support for that target. The matrix is per **binding**; the
Native plugins table below it is per **artifact**. Use them together:

1. Find your binding's row and check the cell for your target platform.
2. Find the matching Platform row in the Native plugins table for the artifact
   name, ABI, signing requirement, and size.

If a target is supported in the matrix but you cannot find a row for it in the
Native plugins table, the binding does not load a separate binary on that target
— WebGL being the usual case, where the native side is linked in at build time.

## Pinning the FFI core version alongside the package version

There are two versions in play, and the console shows both:

- the **package version**, in the `Version` column of the Package catalog row;
- the **FFI core version**, in the `Wire core` KPI at the top of the screen.

They move independently. A package update can carry a different core, and the
header that your native code compiles against comes from the package. For
reproducible builds, pin the package exactly in the project manifest rather than
letting UPM resolve a range:

```json
// Packages/manifest.json
{
  "dependencies": {
    "<pkg from the SDK row>": "<version from the Version column>"
  }
}
```

Then record the core version from the `Wire core` KPI in the same commit — in a
lockfile comment, a build note, or your own manifest — so that a later
`EntryPointNotFoundException` can be traced to a core bump rather than to a
placement mistake. If you vendor binaries by hand instead of installing through
UPM, take the header and every platform artifact from a single download so that
all of them carry the same core.

## Related

- [Telemetry](/platform/telemetry) — the transport-level metrics and traces the core emits
- [Authentication](/concepts/authentication) — relay tokens and the namespace scope the transport presents on connect
