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: