Hooks System
Hooks are async functions in your ota-updates.config.js that run at key points during the publish flow. Use them to validate, transform, or extend the default behavior — for example, running tests before production publishes, customizing version formats, or sending Slack notifications after a release.
Available Hooks
javascript
export default {
hooks: {
beforePublish: async (context) => {},
afterPublish: async (version, context) => {},
onError: async (error, context) => {},
generateVersion: async (context) => {},
generateChangelog: async (context) => {},
},
};Hook Signatures
beforePublish(context)
Runs before version increment and publish.
context:
ts
{
currentVersion: OTAVersionData | null;
channel: string;
changelog: string[];
dryRun: boolean;
cwd: string;
}Can return optional overrides:
ts
{
changelog?: string[];
message?: string;
version?: string | Partial<OTAVersionData>;
}afterPublish(version, context)
Runs after successful publish.
version is the final resolved version object.context:
ts
{
channel: string;
message: string;
cwd: string;
dryRun: boolean;
}onError(error, context)
Runs on publish failure.
context:
ts
{
channel?: string;
cwd: string;
}generateVersion(context)
Required when versionStrategy: 'custom'.
context:
ts
{
currentVersion: OTAVersionData | null;
channel: string;
changelog: string[];
cwd: string;
buildNumber: number;
baseVersion: string;
parsedBaseVersion: { major: number; minor: number; patch: number };
templateVars: {
major: number;
minor: number;
patch: number;
channel: string;
channelAlias: string;
build: number;
timestamp: string;
};
defaultVersion: string;
}Return value:
string(full version)Partial<OTAVersionData>- full
OTAVersionData
generateChangelog(context)
Required when changelog.source: 'custom'.
context:
ts
{
currentVersion: OTAVersionData | null;
channel: string;
cwd: string;
}Must return string[].
Practical Examples
1. Short Production Versions + Custom Message
javascript
export default {
hooks: {
beforePublish: async ({ channel, changelog }) => {
if (channel === 'production') {
return {
message: `prod release: ${changelog[0] || 'Update'}`,
};
}
},
},
};2. Custom Version Strategy
javascript
export default {
versionStrategy: 'custom',
hooks: {
generateVersion: async ({ templateVars }) => {
return `r${templateVars.major}.${templateVars.minor}.${templateVars.build}`;
},
},
};3. Custom Changelog Source
javascript
export default {
changelog: {
source: 'custom',
},
hooks: {
generateChangelog: async ({ channel }) => {
return [`Manual curated release for ${channel}`];
},
},
};4. Validation Gate
javascript
export default {
hooks: {
beforePublish: async ({ channel, changelog }) => {
if (channel === 'production' && changelog.length === 0) {
throw new Error('Production publish requires changelog entries');
}
},
},
};5. Post-Publish Notification
javascript
export default {
hooks: {
afterPublish: async (version, context) => {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `OTA update published: v${version.version} → ${context.channel}`,
}),
});
},
},
};6. Run Tests Before Production
javascript
export default {
hooks: {
beforePublish: async ({ channel }) => {
if (channel === 'production') {
const { execa } = await import('execa');
await execa('npm', ['test'], { stdio: 'inherit' });
}
},
},
};Best Practices
- Keep hooks deterministic and fast.
- Throw errors only for hard-stop validation failures.
- Use
beforePublishreturn overrides sparingly and clearly. - Keep custom generators pure (no side effects when possible).
- Use environment variables for secrets.
Next Steps
Want to feel the package end-to-end first? Try the Expo Showcase Demo.