> For the complete documentation index, see [llms.txt](https://docs.realms.today/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.realms.today/developer-resources/custom-plugin.md).

# Custom Plugin

## Create a Custom Plugin

SPL Governance uses an open/close plugin architecture that allows you to customize how voting power is determined. Instead of the default "deposit tokens = voting power" model, you can build plugins that implement any custom logic: NFT voting, token locking, staking, quadratic voting, prediction markets, and more.

### How Plugins Work

Plugins (also called "addins") are standalone Solana programs that integrate with SPL Governance through two standardized interfaces:

1. **VoterWeightRecord** - Provides individual voter weight to the governance program
2. **MaxVoterWeightRecord** - Provides the maximum possible voter weight for quorum calculations

When a Realm is configured with a plugin, the governance program reads the `VoterWeightRecord` account instead of using deposited token amounts for voting power. This means your plugin has full control over how voting power is computed.

### The VoterWeightRecord Interface

Your plugin must create and maintain `VoterWeightRecord` accounts that conform to this structure:

```rust
pub struct VoterWeightRecord {
    /// Discriminator: sha256("account:VoterWeightRecord")[..8]
    pub account_discriminator: [u8; 8],

    /// The Realm this record belongs to
    pub realm: Pubkey,

    /// The governing token mint (community or council)
    pub governing_token_mint: Pubkey,

    /// The voter's wallet address
    pub governing_token_owner: Pubkey,

    /// The computed voter weight
    pub voter_weight: u64,

    /// Optional: slot when this weight expires (None = never expires)
    pub voter_weight_expiry: Option<Slot>,

    /// Optional: the specific governance action this weight applies to
    pub weight_action: Option<VoterWeightAction>,

    /// Optional: the target account for the action (e.g., a specific Proposal)
    pub weight_action_target: Option<Pubkey>,

    pub reserved: [u8; 8],
}
```

The `VoterWeightAction` enum defines which governance actions the weight applies to:

```rust
pub enum VoterWeightAction {
    CastVote,          // Voting on a proposal
    CommentProposal,   // Commenting on a proposal
    CreateGovernance,  // Creating a new governance
    CreateProposal,    // Creating a new proposal
    SignOffProposal,   // Signing off a proposal
}
```

### The MaxVoterWeightRecord Interface

If your plugin needs to define the maximum possible voting weight (used for quorum calculations), implement:

```rust
pub struct MaxVoterWeightRecord {
    /// Discriminator: sha256("account:MaxVoterWeightRecord")[..8]
    pub account_discriminator: [u8; 8],

    pub realm: Pubkey,
    pub governing_token_mint: Pubkey,

    /// The maximum voter weight
    pub max_voter_weight: u64,

    /// Optional expiry slot
    pub max_voter_weight_expiry: Option<Slot>,

    pub reserved: [u8; 8],
}
```

### Building Your Plugin: Step by Step

#### 1. Create the On-Chain Program

Your plugin is a standard Solana program (Anchor or native). It must:

* Store voter weight data in `VoterWeightRecord` accounts with the correct discriminator
* Provide an instruction to update/refresh the voter weight (commonly called `UpdateVoterWeightRecord`)
* Optionally provide a `MaxVoterWeightRecord` if your model needs custom quorum logic

**Recommended structure using Anchor:**

```
my-plugin/
  programs/
    my-plugin/
      src/
        lib.rs          # Program entrypoint
        instructions/
          create_registrar.rs
          create_voter_weight_record.rs
          update_voter_weight_record.rs
        state/
          registrar.rs   # Plugin config per realm
          voter.rs       # Per-voter state
```

A **Registrar** account is a common pattern: it stores the plugin configuration for a specific Realm (e.g., which token mints are accepted, locking parameters, etc.).

#### 2. Key Instructions to Implement

**CreateRegistrar** - Initialize plugin config for a realm:

* Seeds: `['registrar', realm, governing_token_mint]`
* Stores: realm reference, governing token mint, custom config

**CreateVoterWeightRecord** - Create a voter's weight record:

* Seeds: `['voter-weight-record', realm, governing_token_mint, governing_token_owner]`
* Initialize with proper discriminator

**UpdateVoterWeightRecord** - Refresh the voter's weight before governance actions:

* Read your custom state (deposits, locks, NFTs, etc.)
* Compute the weight
* Write it to the `VoterWeightRecord` with the current slot as expiry
* This instruction is called in the same transaction as the governance action

#### 3. Register the Plugin with a Realm

Use `SetRealmConfig` to attach your plugin to a Realm:

```rust
set_realm_config(
    program_id,
    realm,
    realm_authority,
    council_token_mint,
    payer,
    Some(GoverningTokenConfigAccountArgs {
        voter_weight_addin: Some(your_plugin_program_id),
        max_voter_weight_addin: None, // or Some(your_program_id)
        token_type: GoverningTokenType::Liquid,
    }),
    None, // council config
    min_community_weight_to_create_governance,
    community_mint_max_voter_weight_source,
)
```

#### 4. Build the UI Client

If you want your plugin to work with the Realms UI, implement the `VotePlugin` TypeScript interface:

```typescript
export interface VotePlugin {
  showDepositModal: boolean;

  initPlugin(
    rpcEndpoint: string,
    programId: string,
    voter: string,
    realm: string,
    mint: string,
    delegates: string[],
    inputWeightProgramId?: string
  ): Promise<VotePlugin>;

  getVoterWeight(tokenOwnerRecord?: string): Promise<VoteWeight>;

  getDepositInstructions(amount: BN): Promise<PluginInstructions>;
  getWithdrawInstructions(amount: BN, tokenOwnerRecord: string): Promise<PluginInstructions>;

  getDepositMessage(): DepositMessage | null;
  getPluginDepositMint(): string | null;
  getDepositedTokenAmount(): BN | null;

  getUpdateVoterWeightInstructions(
    tokenOwnerRecord?: string,
    actionTarget?: string,
    action?: VoterWeightAction,
    governance?: string,
    proposal?: string
  ): Promise<UpdateInstructions>;
}
```

See the Custom UI Integration guide for details on integrating with the Realms UI.

### Existing Plugin Examples

These plugins are live on mainnet and serve as reference implementations:

| Plugin                     | Description                                  | Program ID                                     |
| -------------------------- | -------------------------------------------- | ---------------------------------------------- |
| VSR (Voter Stake Registry) | Token locking with time-weighted multipliers | `vsr2nfGVNHmSY8uxoBGqq8AQbwz3JwaEaHqGbsTPXqQ`  |
| Bio VSR                    | Bio variant of Voter Stake Registry          | `bioU1oVcFscZa2KwxexVREBVBuRtMBUNdFoGPUSsPnf`  |
| Epicentral VSR             | Epicentral Labs staking-based voting         | `epciLwfT8DePi1ch5FKXvLmwHBknXV9EL42yGqrZPEy`  |
| NFT Voter                  | Vote with NFTs from specific collections     | `GnftV5kLjd67tvHpNGyodwWveEKivz3ZWvvE3Z4xi2iw` |
| Token Voter                | Vote based on token holdings                 | `HA99cuBQCCzZu1zuHN2qBxo2FBo1cxNLwKkdt6Prhy8v` |
| Bonk                       | Bonk token voting                            | `BonKNRsdWbRFkjPRNXfMqGNq5GShqdT2RGKZZ7Pn1LR`  |
| Token Haver                | Vote based on token ownership (haver)        | `HavrKkk4L3mpEgPRD4GZ3RBvE3qLiKrK2xPam8ojDhPg` |
| Gateway                    | Civic Pass identity verification gate        | `GgathUhdrCWRHowoRKACjgWhYHfxCEdBi5ViqYN6HVxk` |
| Quadratic                  | Quadratic voting power distribution          | `quadCSapU8nTdLg73KHDnmdxKnJQsh7GUbu5tZfnRRr`  |
| Pyth                       | Pyth staking-based voting                    | `pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ`  |
| Drift                      | Drift protocol staking voting                | `dVoTE1AJqkZVoE1mPbWcqYPmEEvAUBksHY2NiM2UJQe`  |
| Parcl                      | Parcl governance voting                      | `parCLFza3XxuMnoR8M2LhCXMKP7dSaaUvqjXbKre9UT`  |
| Sowellian                  | Prediction market governance                 | `sowEL1Rtn3p479rg34gW7mVPeCNY58Es5rkLpFsCJAW`  |
| Core Voter                 | Vote with Metaplex Core NFTs                 | `cNFTHBQuERFVrbmks1UzqFQPBHzquRmoLbmgpHBbczF`  |

### Testing Your Plugin

The `governance/addin-mock` directory in the SPL Governance repo contains a mock addin program that can be used as a starting point and for testing:

```rust
pub enum VoterWeightAddinInstruction {
    SetupVoterWeightRecord {
        voter_weight: u64,
        voter_weight_expiry: Option<Slot>,
        weight_action: Option<VoterWeightAction>,
        weight_action_target: Option<Pubkey>,
    },
    SetupMaxVoterWeightRecord {
        max_voter_weight: u64,
        max_voter_weight_expiry: Option<Slot>,
    },
}
```

Use it in integration tests to simulate custom voting weights without deploying a full plugin.

### Plugin Chaining

Plugins can be chained by configuring one plugin to read the `VoterWeightRecord` produced by another as its input. The `VotePlugin` interface supports this via the optional `inputWeightProgramId` parameter in `initPlugin`. For example, a Gateway plugin can wrap a VSR plugin to require identity verification on top of token-locked voting power. In this setup, VSR computes the base voter weight, and Gateway reads that weight and gates it behind a Civic Pass check before producing the final `VoterWeightRecord` consumed by the governance program.
