Skip to main content

Publication of your Plugin into Aragon OSx

How to publish a plugin into Aragon's plugin registry

Once you've deployed your Plugin Setup contract, you will be able to publish your plugin into Aragon's plugin registry so any Aragon DAO can install it.

1. Make sure your plugin is deployed in the right network

Make sure your Plugin Setup contract is deployed in your network of choice (you can find all of the networks we support here). You will need the address of your Plugin Setup contract to be able to publish the plugin into the protocol.

2. Publishing your plugin

Every plugin in Aragon can have future versions, so when publishing a plugin to the Aragon protocol, we're really creating a PluginRepo instance for each plugin, which will contain all of the plugin's versions.

To publish a plugin, we will use Aragon's PluginRepoFactory contract - in charge of creating PluginRepo instances containing your plugin's versions. To do this, we will call its createPluginRepoWithFirstVersion function, which will create the first version of a plugin and add that new PluginRepo address into the PluginRepoRegistry containing all available plugins within the protocol.

You can find all of the addresses of PluginRepoFactory contracts by network here.

To create more versions of your plugin in the future, you'll call on the createVersion function from the PluginRepo instance of your plugin. When you publish your plugin, you'll be able to find the address of your plugin's PluginRepo instance within the transaction data.

Having that said, there's a few ways to publish your plugin into Aragon's protocol:

Option A: Etherscan

Go directly to Etherscan and search for the address of the PluginRepoFactory as found in this list.

Then, go to the "Write Contract" tab and add the attributes requested to call on the createPluginRepoWithFirstVersion function. Make sure you "Connect your Wallet" before hitting "Write" and signing the transaction.


For some context:

  • subdomain: The subdomain name of your plugin. Whatever you write here should be hyphenated (i.e. my-plugin) and should be unique.
  • pluginSetupAddress: The address of your deployed Plugin Setup contract (i.e. 0x3018f7712b77744A31277511609238399f0A26h8).
  • maintainer: The address owner of that plugin (i.e. 0x87789071456774411227751100h89g899fg6679).
  • releaseMetadata: The URI of the data regarding the release of this plugin (i.e. 0x87789071456774411227751100h89g899fg6679). If you don't have any as of now, simply pass 0x00. You get this address by uploading your metadata object to IPFS.
  • buildMetadata: The URI of the data regarding this specific build implementation (i.e. 0x87789071456774411227751100h89g899fg6679). If you don't have any as of now, simply pass 0x00. You get this address by uploading your metadata object to IPFS.

Option B: Programmatically publish with a script

You may want to have a publishing script directly off of your Hardhat project. In that case, you may want to check out our publishing script and additional helpers here.

import buildMetadata1 from '../../contracts/release1/build1/build-metadata.json';
import releaseMetadata1 from '../../contracts/release1/release-metadata.json';
import {
} from '../../utils/helpers';
import {toHex} from '../../utils/ipfs-upload';
import {uploadToIPFS} from '../../utils/ipfs-upload';
import {
} from '@aragon/osx-ethers';
import {DeployFunction} from 'hardhat-deploy/types';
import {HardhatRuntimeEnvironment} from 'hardhat/types';

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const {deployments, network} = hre;
const [deployer] = await hre.ethers.getSigners();

// Get the plugin factory address
let pluginRepoFactoryAddr: string;
if ( === 'localhost' || === 'hardhat' || === 'coverage') {
const hardhatForkNetwork = process.env.HARDHAT_FORK_NETWORK
: 'mainnet';

pluginRepoFactoryAddr = osxContracts[hardhatForkNetwork].PluginRepoFactory;
`Using the ${hardhatForkNetwork} PluginRepoFactory address (${pluginRepoFactoryAddr}) for deployment testing on network ${}`
} else {
pluginRepoFactoryAddr = osxContracts[networkNameMapping[]].PluginRepoFactory;

`Using the ${
} PluginRepoFactory address (${pluginRepoFactoryAddr}) for deployment...`

const pluginRepoFactory = PluginRepoFactory__factory.connect(pluginRepoFactoryAddr, deployer);

// Upload the metadata
const releaseMetadataURI = `ipfs://${await uploadToIPFS(
const buildMetadataURI = `ipfs://${await uploadToIPFS(JSON.stringify(buildMetadata1), false)}`;

console.log(`Uploaded metadata of release 1: ${releaseMetadataURI}`);
console.log(`Uploaded metadata of build 1: ${buildMetadataURI}`);

const pluginName = 'simple-storage';
const pluginSetupContractName = 'SimpleStorageR1B1Setup';

const setupR1B1 = await deployments.get(pluginSetupContractName);

// Create Repo for Release 1 and Build 1
const tx = await pluginRepoFactory.createPluginRepoWithFirstVersion(
const eventLog = await findEventTopicLog(
if (!eventLog) {
throw new Error('Failed to get PluginRepoRegistered event log');

const pluginRepo = PluginRepo__factory.connect(eventLog.args.pluginRepo, deployer);

console.log(`"${pluginName}" PluginRepo deployed at: ${pluginRepo.address} with `);

addDeployedContract(, 'PluginRepo', pluginRepo.address);
addDeployedContract(, pluginSetupContractName, setupR1B1.address);

export default func;
func.tags = ['SimpleStoragePluginRepo', 'PublishSimpleStorageR1B2'];

3. Publishing subsequent builds

When publishing subsquent builds, you want to use the createVersion function in the PluginRepo contract (check out the function's source code here).

Similar as above, you can publish the new version on Etherscan directly or through a publishing script.

© 2023