Skip to content
Intermediate

Add Pallet Instances

IntroductionΒΆ

The Polkadot SDK Parachain Template provides a solid foundation for building custom parachains. While most pallets are typically included as single instances within a runtime, some scenarios benefit from running multiple instances of the same pallet with different configurations. This approach lets you reuse pallet logic without reimplementing it, enabling diverse functionality from a single codebase.

For example, you could create multiple governance councils with different voting rules, or several token systems with distinct parameters. The Polkadot SDK makes this possible through instantiable pallets, which allow multiple independent instances of the same pallet to coexist within a runtime.

This guide demonstrates how to add and configure multiple instances of a pallet to your runtime using pallet-collective as a practical example. The same process applies to other instantiable pallets.

In this guide, you'll learn how to:

  • Identify instantiable pallets and understand their structure.
  • Configure multiple instances of the same pallet with unique parameters.
  • Register multiple pallet instances in your runtime.
  • Run your parachain locally to test multiple pallet instances.

Check PrerequisitesΒΆ

Before you begin, ensure you have:

Understanding Instantiable PalletsΒΆ

Not all pallets support multiple instances. Instantiable pallets are specifically designed to allow multiple independent copies within the same runtime. These pallets include an additional generic parameter I that creates a unique identity for each instance.

Identifying an Instantiable PalletΒΆ

You can identify an instantiable pallet by examining its Pallet struct definition. An instantiable pallet will include both the standard generic T (for the runtime configuration) and the instantiation generic I:

#[pallet::pallet]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);

The I generic parameter:

  • Creates a unique type identity for each pallet instance.
  • Appears throughout the pallet's components (Config trait, storage items, events, errors).
  • Defaults to () (unit type) when only one instance is needed.
  • Must be explicitly specified when creating multiple instances.

How Instance Generics WorkΒΆ

The instantiation generic I affects how the pallet's types are structured:

  • Config trait: trait Config<I: 'static = ()> - accepts the instance parameter.
  • Storage items: Automatically namespaced by instance to prevent conflicts.
  • Events: Event<T, I> - includes instance information.
  • Calls: Call<T, I> - dispatched to the correct instance.

This design ensures that multiple instances of the same pallet maintain completely separate states and don't interfere with each other.

Add Multiple Instances of a Pallet to Your RuntimeΒΆ

Adding multiple pallet instances involves the same basic steps as adding a single pallet, but with specific configuration for each instance.

In this example, you'll add two instances of pallet-collective to create different governance bodies.

Add the Pallet as a DependencyΒΆ

First, ensure the instantiable pallet is available in your runtime dependencies. For pallet-collective, add it as a feature of the polkadot-sdk dependency:

  1. Open the runtime/Cargo.toml file.
  2. Locate the [dependencies] section.
  3. Find the polkadot-sdk dependency.
  4. Add pallet-collective to the features array:

    Cargo.toml
    polkadot-sdk = { workspace = true, features = [
        "pallet-collective",
        "cumulus-pallet-aura-ext",
        "cumulus-pallet-session-benchmarking",
        # ... other features
    ], default-features = false }
    

Enable Standard Library FeaturesΒΆ

Ensure the pallet's standard library features are enabled for native builds:

  1. In the runtime/Cargo.toml file, locate the [features] section.
  2. Ensure polkadot-sdk/std is included in the std array:

    Cargo.toml
    [features]
    default = ["std"]
    std = [
        "codec/std",
        "cumulus-pallet-parachain-system/std",
        "log/std",
        "polkadot-sdk/std",
        "scale-info/std",
        # ... other features
    ]
    

Review the Config TraitΒΆ

Before configuring multiple instances, understand what the pallet requires. The pallet-collective Config trait is defined with the instance generic:

pub trait Config<I: 'static = ()>: frame_system::Config {
    /// The runtime origin type
    type RuntimeOrigin: From<RawOrigin<Self::AccountId, I>>;

    /// The runtime call type
    type Proposal: Parameter 
        + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
        + From<frame_system::Call<Self>>;

    /// The overarching event type
    type RuntimeEvent: From<Event<Self, I>> 
        + IsType<<Self as frame_system::Config>::RuntimeEvent>;

    /// Duration in blocks for a motion to remain active
    type MotionDuration: Get<BlockNumberFor<Self>>;

    /// Maximum number of proposals allowed at once
    type MaxProposals: Get<u32>;

    /// Maximum number of members in the collective
    type MaxMembers: Get<u32>;

    /// Default voting strategy when a member abstains
    type DefaultVote: DefaultVote;

    /// Origin that can modify the members
    type SetMembersOrigin: EnsureOrigin<Self::RuntimeOrigin>;

    /// Weight information for extrinsics
    type WeightInfo: WeightInfo;

    /// Maximum weight for a proposal
    type MaxProposalWeight: Get<Weight>;

    /// Origin that can disapprove proposals
    type DisapproveOrigin: EnsureOrigin<Self::RuntimeOrigin>;

    /// Origin that can kill proposals
    type KillOrigin: EnsureOrigin<Self::RuntimeOrigin>;

    /// Consideration mechanism (e.g., deposits)
    type Consideration: Consideration<Self::AccountId>;
}

This configuration enables the collective pallet to manage a group of accounts that can propose and vote on proposals together.

Define Pallet ParametersΒΆ

Before implementing the Config trait for each instance, define the common parameters that both instances will use. These parameters are defined once and can be shared across instances or customized per instance.

To define pallet parameters:

  1. Open the runtime/src/configs/mod.rs file.
  2. Add parameter type definitions for the collective pallet:

    runtime/src/configs/mod.rs
    parameter_types! {
        pub const MotionDuration: BlockNumber = 24 * HOURS;
        pub const MaxProposals: u32 = 100;
        pub const MaxMembers: u32 = 100;
        pub MaxProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block;
    }
    

Tip

You can define separate parameters for each instance if you need different configurations. For example, you might want a technical committee with a shorter motion duration and fewer members than a general council.

Import Instance TypesΒΆ

Each pallet instance needs a unique type identifier. The Polkadot SDK provides numbered instance types (Instance1, Instance2, etc.) in the frame_support::instances module.

In the runtime/src/configs/mod.rs file, import the instance types:

runtime/src/configs/mod.rs
use frame_support::instances::{Instance1, Instance2};

These instance types:

  • Create distinct identities for each pallet instance.
  • Are used when implementing the Config trait and adding to the runtime construct.
  • Are provided by frame_support, not by individual pallets.

Implement Config Trait for First InstanceΒΆ

Now implement the Config trait for your first instance. The implementation includes the instance type as a generic parameter.

In the runtime/src/configs/mod.rs file, add the following implementation:

runtime/src/configs/mod.rs
/// Configure the Technical Committee collective
impl pallet_collective::Config<Instance1> for Runtime {
    type RuntimeOrigin = RuntimeOrigin;
    type Proposal = RuntimeCall;
    type RuntimeEvent = RuntimeEvent;
    type MotionDuration = MotionDuration;
    type MaxProposals = MaxProposals;
    type MaxMembers = MaxMembers;
    type DefaultVote = pallet_collective::MoreThanMajorityThenPrimeDefaultVote;
    type SetMembersOrigin = EnsureRoot<AccountId>;
    type WeightInfo = pallet_collective::weights::SubstrateWeight<Runtime>;
    type MaxProposalWeight = MaxProposalWeight;
    type DisapproveOrigin = EnsureRoot<Self::AccountId>;
    type KillOrigin = EnsureRoot<Self::AccountId>;
    type Consideration = ();
}

Key configuration details:

  • RuntimeOrigin, RuntimeCall, RuntimeEvent: Connect to the runtime's aggregated types.
  • MotionDuration: How long proposals remain active (5 days in this example).
  • MaxProposals: Maximum number of active proposals (100).
  • MaxMembers: Maximum collective members (100).
  • DefaultVote: Voting strategy when members abstain (majority with prime member tiebreaker).
  • SetMembersOrigin: Who can modify membership (root in this example).
  • MaxProposalWeight: Maximum computational weight for proposals (50% of block weight).
  • DisapproveOrigin/KillOrigin: Who can reject proposals (root in this example).
  • Consideration: Deposit mechanism (none in this example).

Implement Config Trait for Second InstanceΒΆ

Implement the Config trait for your second instance with the same or a different configuration.

In the runtime/src/configs/mod.rs file, add the following implementation:

runtime/src/configs/mod.rs
/// Configure the Council collective
impl pallet_collective::Config<Instance2> for Runtime {
    type RuntimeOrigin = RuntimeOrigin;
    type Proposal = RuntimeCall;
    type RuntimeEvent = RuntimeEvent;
    type MotionDuration = MotionDuration;
    type MaxProposals = MaxProposals;
    type MaxMembers = MaxMembers;
    type DefaultVote = pallet_collective::MoreThanMajorityThenPrimeDefaultVote;
    type SetMembersOrigin = EnsureRoot<AccountId>;
    type WeightInfo = pallet_collective::weights::SubstrateWeight<Runtime>;
    type MaxProposalWeight = MaxProposalWeight;
    type DisapproveOrigin = EnsureRoot<Self::AccountId>;
    type KillOrigin = EnsureRoot<Self::AccountId>;
    type Consideration = ();
}

Tip

While this example uses identical configurations for both instances, you can customize each instance's parameters to serve different purposes. For example, you might configure the technical committee with stricter voting requirements or shorter motion durations than the general council.

Add Instances to Runtime ConstructΒΆ

The final configuration step is registering both pallet instances in the runtime construct. Each instance needs a unique pallet index and must specify its instance type.

To add the pallet instances to the runtime construct:

  1. Open the runtime/src/lib.rs file.
  2. Locate the #[frame_support::runtime] section.
  3. Add both pallet instances with unique indices:

    runtime/src/lib.rs
    #[frame_support::runtime]
    mod runtime {
        #[runtime::runtime]
        #[runtime::derive(
            RuntimeCall,
            RuntimeEvent,
            RuntimeError,
            RuntimeOrigin,
            RuntimeTask,
            RuntimeFreezeReason,
            RuntimeHoldReason,
            RuntimeSlashReason,
            RuntimeLockId,
            RuntimeViewFunction
        )]
        pub struct Runtime;
    
        #[runtime::pallet_index(0)]
        pub type System = frame_system;
    
        #[runtime::pallet_index(1)]
        pub type ParachainSystem = cumulus_pallet_parachain_system;
    
        // ... other pallets
    
        #[runtime::pallet_index(50)]
        pub type TechnicalCommittee = pallet_collective<Instance1>;
    
        #[runtime::pallet_index(51)]
        pub type Council = pallet_collective<Instance2>;
    }
    

Important considerations when adding instances:

  • Unique indices: Each instance must have a different pallet_index.
  • Instance type: Specify the instance type in angle brackets (e.g., <Instance1>).
  • Descriptive names: Use names that reflect the instance's purpose (e.g., TechnicalCommittee, Council).
  • Index management: Track which indices are used to avoid conflicts.

Warning

Duplicate pallet indices will cause compilation errors. Keep a list of used indices to prevent conflicts when adding new pallets or instances.

Verify the Runtime CompilesΒΆ

After adding and configuring both pallet instances, verify that everything is set up correctly by compiling the runtime from your project's root directory:

cargo build --release

Ensure the build completes successfully without errors.

This command validates:

  • All pallet instances are properly configured
  • No index conflicts exist
  • Type definitions are correct
  • Dependencies are properly resolved

Run Your Chain LocallyΒΆ

Now that you've added multiple pallet instances to your runtime, you can launch your parachain locally to test the new functionality using the Polkadot Omni Node. For instructions on setting up the Polkadot Omni Node and Polkadot Chain Spec Builder, refer to the Set Up the Parachain Template page.

Generate a Chain SpecificationΒΆ

Create a new chain specification file with the updated runtime containing both pallet instances by running the following command from your project's root directory:

chain-spec-builder create -t development \
    --relay-chain paseo \
    --para-id 1000 \
    --runtime ./target/release/wbuild/parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm \
    named-preset development

This command generates a chain specification file (chain_spec.json) for your parachain with the updated runtime.

Start the Parachain NodeΒΆ

Launch the parachain using the Polkadot Omni Node with the generated chain specification:

polkadot-omni-node --chain ./chain_spec.json --dev

Verify the node starts successfully and begins producing blocks. You should see log messages indicating that both pallet instances are initialized.

Interact with Both Pallet InstancesΒΆ

Use the Polkadot.js Apps interface to verify you can interact with both pallet instances independently.

To interact with the pallet instances:

  1. Navigate to Polkadot.js Apps.
  2. Ensure you're connected to your local node at ws://127.0.0.1:9944.
  3. Go to the Developer > Extrinsics tab.
  4. In the submit the following extrinsic section, open the pallet dropdown. Verify that both pallet instances appear and contain the expected extrinsics.

    Select technicalCommittee and open the extrinsics dropdown.

    Select council and open the extrinsics dropdown.

Each instance should display the following extrinsics (this is not an exhaustive list):

  • close(proposalHash, index, proposalWeightBound, lengthBound): Close voting.
  • propose(threshold, proposal, lengthBound): Submit a proposal.
  • setMembers(newMembers, prime, oldCount): Update membership.
  • vote(proposal, index, approve): Vote on a proposal.

Test Instance IndependenceΒΆ

Verify that both instances operate independently by testing their separate functionality.

To test instance independence:

  1. In Polkadot.js Apps, go to Developer > Chain state.
  2. Query storage for each instance:

    Select technicalCommittee > members() to view technical committee members.

    Select council > members() to view council members.

  3. Verify that:

    • Each instance maintains separate storage.
    • Changes to one instance don't affect the other.
    • Both instances can process proposals simultaneously.

You can now use both collective instances for different governance purposes in your parachain, such as technical decisions that require expertise and general governance decisions that require broader consensus.

Where to Go NextΒΆ

  • Guide Make a Custom Pallet


    Learn how to create custom pallets using FRAME.

    Reference

Last update: February 13, 2026
| Created: January 14, 2026