Coda

Configuration

Customize Coda generation with coda.config.mjs

Coda works out of the box with sensible defaults, but you can customize its behavior with a coda.config.mjs file.

Initialize Configuration

Create a configuration file using the init command:

coda init

This creates a coda.config.mjs file with helpful comments and examples.

Configuration Options

idlPath

  • Type: string | string[]
  • Default: "./target/idl/program.json"
  • Description: Path to your Anchor IDL file(s). Can be a single path or array for multiple IDLs.

outputDir

  • Type: string
  • Default: "./src/generated"
  • Description: Directory where generated TypeScript files will be placed

docs

  • Type: { path?: string, npmPackageName?: string }
  • Default: {}
  • Description: Documentation-specific options
    • path: Directory where generated markdown documentation will be placed (default: "./docs")
    • npmPackageName: NPM package name to display badge and link in documentation

visitors

  • Type: Visitor[] | (context) => Visitor[]
  • Default: []
  • Description: Codama visitors to transform the AST. Can be an array or function that receives the IDL context.

Basic Configuration

Simple configuration with custom paths:

import { defineConfig } from "@macalinao/coda";

export default defineConfig({
  idlPath: "./idls/my_program.json",
  outputDir: "./src/client",
});

Multiple IDLs

Generate clients from multiple programs in one project:

import { defineConfig } from "@macalinao/coda";

export default defineConfig({
  idlPath: [
    "./idls/program_a.json",
    "./idls/program_b.json",
    "./idls/program_c.json",
  ],
  outputDir: "./src/generated",
});

Important: Handling Naming Conflicts

When generating clients from multiple IDLs, be aware of potential naming conflicts:

  • Naming conflicts will NOT throw errors during code generation
  • Conflicting names will be silently overwritten by the last processed IDL
  • This can lead to unexpected behavior and missing types/instructions

To avoid conflicts, use the renameVisitor from @macalinao/codama-rename-visitor:

import { renameVisitor } from "@macalinao/codama-rename-visitor";

export default defineConfig({
  visitors: [
    renameVisitor({
      // Rename conflicting instructions in specific programs
      programA: {
        instructions: {
          transfer: "transferA",  // Rename transfer to transferA
          deposit: "depositA",    // Rename deposit to depositA
        },
        definedTypes: {
          account: "accountA",    // Rename account type to accountA
        },
      },
    }),
  ],
});

See the Quarry configuration for a real-world example where instructions like claimRewards, initMiner, and withdrawTokens were renamed to avoid conflicts between the quarryMine and quarryMergeMine programs.

Custom PDAs

Define program-derived addresses with custom seeds:

import { defineConfig } from "@macalinao/coda";
import {
  addPdasVisitor,
  constantPdaSeedNodeFromString,
  publicKeyTypeNode,
  variablePdaSeedNode,
} from "codama";

const addCustomPDAsVisitor = addPdasVisitor({
  tokenMetadata: [
    {
      name: "metadata",
      seeds: [
        constantPdaSeedNodeFromString("utf8", "metadata"),
        variablePdaSeedNode(
          "programId",
          publicKeyTypeNode(),
          "The address of the program",
        ),
        variablePdaSeedNode(
          "mint",
          publicKeyTypeNode(),
          "The address of the mint account",
        ),
      ],
    },
  ],
});

export default defineConfig({
  idlPath: "./idls/token_metadata.json",
  outputDir: "./src/generated",
  visitors: [addCustomPDAsVisitor],
});

Advanced Example: Quarry Configuration

Real-world example with multiple IDLs, PDAs, and rename visitors:

import {
  addPdasVisitor,
  constantPdaSeedNodeFromString,
  defineConfig,
  publicKeyTypeNode,
  variablePdaSeedNode,
} from "@macalinao/coda";
import { renameVisitor } from "@macalinao/codama-rename-visitor";

export default defineConfig({
  // Multiple IDLs from the same project
  idlPath: [
    "./idls/quarry_mine.json",
    "./idls/quarry_mint_wrapper.json",
    "./idls/quarry_operator.json",
    "./idls/quarry_merge_mine.json",
    "./idls/quarry_redeemer.json",
    "./idls/quarry_registry.json",
  ],
  outputDir: "./src/generated",

  visitors: [
    // Add PDAs for each program
    addPdasVisitor({
      quarryMine: [
        {
          name: "rewarder",
          seeds: [
            constantPdaSeedNodeFromString("utf8", "Rewarder"),
            variablePdaSeedNode("base", publicKeyTypeNode()),
          ],
        },
        {
          name: "quarry",
          seeds: [
            constantPdaSeedNodeFromString("utf8", "Quarry"),
            variablePdaSeedNode("rewarder", publicKeyTypeNode()),
            variablePdaSeedNode("tokenMint", publicKeyTypeNode()),
          ],
        },
        {
          name: "miner",
          seeds: [
            constantPdaSeedNodeFromString("utf8", "Miner"),
            variablePdaSeedNode("quarry", publicKeyTypeNode()),
            variablePdaSeedNode("authority", publicKeyTypeNode()),
          ],
        },
      ],
      quarryMergeMine: [
        {
          name: "mergePool",
          seeds: [
            constantPdaSeedNodeFromString("utf8", "MergePool"),
            variablePdaSeedNode("primaryMint", publicKeyTypeNode()),
          ],
        },
      ],
    }),
    
    // Rename instructions to avoid conflicts between programs
    renameVisitor({
      quarryMergeMine: {
        instructions: {
          claimRewards: "claimRewardsMM",
          initMiner: "initMinerMM",
          withdrawTokens: "withdrawTokensMM",
        },
        definedTypes: {
          claimEvent: "claimEventMM",
        },
      },
    }),
  ],
});

Using Visitor Functions

Access the IDL context in your visitors:

import { defineConfig } from "@macalinao/coda";
import { someCustomVisitor } from "./visitors/custom.js";

export default defineConfig({
  visitors: ({ idl, idls }) => {
    // Access parsed IDL(s) to customize visitors
    console.log(`Generating for ${idl.name}`);
    
    return [
      someCustomVisitor(idl),
      // Add more visitors based on IDL content
    ];
  },
});

Command Line Arguments

Configuration file settings can be overridden via command line:

Generate Command

# Override IDL path
coda generate --idl ./other/idl.json
coda generate -i ./other/idl.json

# Override output directory
coda generate --output ./other/output
coda generate -o ./other/output

# Use different config file
coda generate --config ./custom.config.mjs
coda generate -c ./custom.config.mjs

Docs Command

# Override IDL path
coda docs --idl ./other/idl.json
coda docs -i ./other/idl.json

# Use different config file
coda docs --config ./custom.config.mjs
coda docs -c ./custom.config.mjs

Priority Order

Settings are applied in this priority order (highest to lowest):

  1. Command line arguments
  2. Configuration file (coda.config.mjs)
  3. Default values

Integration with Build Tools

Add to your package.json scripts:

{
  "scripts": {
    "codegen": "coda generate",
    "prebuild": "bun run codegen",
    "dev": "bun run codegen && next dev",
    "clean": "rm -rf ./src/generated"
  }
}

Troubleshooting

Config not loading

  • Ensure file is named coda.config.mjs with .mjs extension
  • Check that you're using ES module syntax (export default)
  • Verify the config file is in your project root or specified via --config

Multiple IDL conflicts

  • Use renameVisitor to rename conflicting instructions/types
  • Generate to separate output directories
  • Consider splitting into multiple config files

Visitor errors

  • Ensure visitors are compatible with your Codama version
  • Check that visitor functions return valid visitor objects
  • Use the context parameter to access IDL information