Skip to content

How to add a custom pre/post deployment step

A project often needs logic that runs around a deployment but is specific to that project — assign a permission set, reschedule a job, call an external system. The template gives you two custom-step hooks for exactly this, so you never fork the orchestrator. For what makes these hooks different from a deployment workaround, see workarounds vs. custom steps.

The two hooks

Under scripts/dia-scripts/deploy/, in the order they run — see the deploy hook sequence for where the metadata pre/post folders fit around them:

Hook module Runs
custom-steps-pre-deployment.mjs before the main deployment, after the pre/ folder's metadata deploys
custom-steps-post-deployment.mjs last, after the post/ folder's metadata deploys

They are woven into the deploy orchestrator and org scratch-create already. Each one silently no-ops if you leave it empty (the module resolves lazily and its absence is skipped), so you only edit the hook you need.

Add your logic

Open the relevant hook and put your steps in its run(). This example re-schedules a scheduled Apex job after deploy:

// scripts/dia-scripts/deploy/custom-steps-post-deployment.mjs
import { parseArgs } from "node:util";
import { runSalesforceCommand } from "../sf-cli.mjs";
import { printHeader } from "../utils/log.mjs";
import { isStandaloneRun } from "./workaround-module.mjs";

export function parseArgumentsToOptions(argv) {
    const { values } = parseArgs({
        args: argv,
        options: {},
        strict: false,
        allowPositionals: true,
    });
    return values;
}

export async function run({ targetOrg } = {}) {
    // Deploying a Schedulable class does not reschedule its CronTrigger; re-schedule it on the fresh code.
    printHeader("Post-deployment: reschedule the nightly loyalty-tier batch");
    await runSalesforceCommand([
        "apex",
        "run",
        "--file",
        "scripts/apex/reschedule-loyalty-tier-batch.apex",
        "--target-org",
        targetOrg,
    ]);
}

if (isStandaloneRun(import.meta))
    run(parseArgumentsToOptions(process.argv.slice(2))).catch((err) => {
        console.error(`[***] ${err?.message ?? err}`);
        process.exit(err?.exitCode || 1);
    });

scripts/apex/reschedule-loyalty-tier-batch.apex aborts any existing CronTrigger for the job and calls System.schedule(...) again.

Use the shared library — runSalesforceCommand for sf calls, utils/log.mjs for output — so your step behaves like the rest of the toolchain (JSON handling, redaction, debug output). The orchestrator calls run() with the parsed CLI args merged with { targetOrg }, the org the deploy just ran against; a hook that ignores the argument still works and falls back to the sf CLI's own default org.

Skip the hooks for one run

Pass --skip-hooks to a deploy when you need the raw deployment without the custom steps:

npm run deploy -- --skip-hooks