> ## Documentation Index
> Fetch the complete documentation index at: https://cordial.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing a plugin

> What a Cordial plugin is for, how it talks to Cordial, and the shortest path from an empty folder to a working one.

## What plugins are for

A plugin extends **Cordial**: it reacts to what the client is doing and asks Cordial to do something about it. Discord status from the current game, a FastFlag preset, a desktop notification when a server changes, a texture pack. If all you need is to apply something once at startup, a plain file is usually enough. Code earns its place when it watches, decides and acts.

<Info>
  A plugin can never reach into Roblox. There is no API for running code in the game, reading or writing its memory, or injecting scripts, and there never will be. Those things are absent, not disabled, so no plugin can ask for them. Why: [ADR-001](/adr/ADR-001-in-process-hooking), [ADR-003](/adr/ADR-003-plugin-isolation).
</Info>

## How it works

A plugin is a folder with a `plugin.json` and, usually, one TypeScript module. Cordial runs the module under Deno with **no** Deno permissions, so it cannot open a file, a socket or a program. It talks to Cordial in newline-delimited JSON over standard input and output.

Everything a plugin does is a request to Cordial, carried out only if the user granted the [capability](/plugin-api/capabilities#the-fifteen-capabilities) it needs, in that profile. The plugin never holds the resource: to set Discord status it sends the text, and Cordial owns the connection. Why: [ADR-007](/adr/ADR-007-host-resources-are-brokered).

## A plugin in five minutes

<Steps>
  <Step title="Make the folder">
    ```text theme={null}
    ~/.local/share/cordial/plugins/hello/
    ├── plugin.json
    └── main.ts
    ```

    Keep the folder name and the `id` identical. Grants and settings key off
    the `id`; the FastFlag layer is read back by folder name.
  </Step>

  <Step title="Write the manifest">
    ```json plugin.json theme={null}
    {
      "id": "hello",
      "name": "Hello",
      "version": "1.0.0",
      "entry": "main.ts",
      "capabilities": ["log"]
    }
    ```

    `capabilities` is what you request. What the plugin gets is what the user
    approves.
  </Step>

  <Step title="Write the module">
    ```ts main.ts theme={null}
    const enc = new TextEncoder();
    const dec = new TextDecoder();
    let nextId = 1;
    const pending = new Map<number, (msg: any) => void>();

    // One reader for the whole process: replies and pushes share the stream.
    (async () => {
      let buf = "";
      for await (const chunk of Deno.stdin.readable) {
        buf += dec.decode(chunk);
        let i: number;
        while ((i = buf.indexOf("\n")) >= 0) {
          const line = buf.slice(0, i);
          buf = buf.slice(i + 1);
          if (!line.trim()) continue;
          const msg = JSON.parse(line);
          if (msg.id === undefined) continue; // a push; a reply always has an id
          pending.get(msg.id)?.(msg);
          pending.delete(msg.id);
        }
      }
    })();

    function call(method: string, params: unknown = {}): Promise<any> {
      const id = nextId++;
      const p = new Promise<any>((resolve) => pending.set(id, resolve));
      Deno.stdout.write(enc.encode(JSON.stringify({ id, method, params }) + "\n"));
      return p;
    }

    await call("log.write", { message: "hello from a plugin" });
    ```
  </Step>

  <Step title="Grant it and run">
    Open **Settings → Plugins**, switch on **Use Plugins**, and turn on `log`
    on the plugin's row. A plugin with nothing granted is never started.
    Press Play and look for the line in Cordial's output.
  </Step>
</Steps>

<Warning>
  Never write to standard output except protocol lines. `console.log` goes to the
  wire, and one unparseable line ends the conversation: Cordial stops the plugin.
  Use `console.error` for debugging; it lands in Cordial's own output.
</Warning>

## Developing without packaging

Instead of copying into the plugins folder, use **Settings → Plugins → Add a plugin folder…** and pick the folder that holds `plugin.json`. It is listed as **Development**, and edits inside it reload as you save. Adding or removing the folder takes effect the next time you press Play. FastFlags written with `flags.set` wait for the next launch too, because Roblox reads most flags once at startup. Details: [Installing plugins](/plugins#load-a-plugin-from-a-folder).

## What a plugin can use

| Area | Capabilities | Reference |
| - | - | - |
| FastFlags | `flags.read`, `flags.write` | [FastFlags](/plugin-api/flags) |
| The client | `lifecycle.read`, `state.read` | [Events](/plugin-api/events) |
| Effects | `presence.set`, `notify.send`, `url.open`, `log` | [The rest of the surface](/plugin-api/surface) |
| Assets | `assets.override`, or an `overlay/` folder with no code | [Asset overrides](/asset-overrides) |
| Its own data | `settings.read`, `settings.write` | [Settings and preferences](/plugin-api/surface#settings-and-preferences) |
| Other plugins | `events.declare`, `events.publish`, `events.subscribe` | [Plugin-declared events](/plugin-api/events#plugin-declared-events) |

A plugin can also declare **preferences** (switches, numbers, choices, text) in
`plugin.json`. Cordial draws the settings page and hands the answers to the
plugin read-only.

Not available, by design: drawing over the game, adding controls to Cordial's
window, and changing a FastFlag in a running game. Two declared events,
`client.ready` and `window.resized`, are not sent by anything yet.

## Sharing a plugin

Pack the folder's contents, with `plugin.json` at the top, as a `.tar.zst`
archive. Users install it from **Settings → Plugins → Install from a file**. A
plugin that contains code installs switched off, and nothing is granted until
the user says so.

There is a signed index format and an installer for it, but no index is
running, so for now plugins are shared as files.

## Examples to read

The four built-in plugins are small and real:
[`flag-inspector`](https://github.com/luohoa97/cordial/tree/main/plugins/flag-inspector)
(the shortest), [`fps-flex`](https://github.com/luohoa97/cordial/tree/main/plugins/fps-flex)
(preferences and pushes), [`discord-presence`](https://github.com/luohoa97/cordial/tree/main/plugins/discord-presence)
and [`hide-gui`](https://github.com/luohoa97/cordial/tree/main/plugins/hide-gui).
The [plugin API reference](/plugin-api) covers every method, event and refusal.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.