This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
PostHog recipes for EAS Workflows
Edit page
A guided path from marking your first deploy in PostHog to gated rollouts, automatic kill switches, and approval-gated releases with EAS Workflows.
EAS Workflows can run PostHog actions as steps in your CI pipeline. Each recipe below is a complete workflow you can copy as-is, and together they build up to a full progressive delivery pipeline:
- Product analytics: Mark a deploy, Annotate a release, and Announce a release once users adopt it
- Feature flags: Release a feature behind a flag, Roll out or roll back on your error rate, Take a flag to full rollout with human approval, and turn a feature off manually or automatically on an error spike
- Error tracking: Upload source maps for readable stack traces, and roll out an EAS Update channel or revert on failure
A compact list of more recipes covers narrower needs. For every input each function accepts, see the EAS Workflows syntax reference.
Get started
One command sets up everything these recipes need. Run it from your project directory:
It links a PostHog project to your Expo project and saves the credentials these functions read as EAS environment variables. The Using PostHog guide walks through the full setup.
When the command asks for a personal API key, create it with the "Source map upload" preset plus the feature_flag:read, feature_flag:write, query:read, and annotation:write scopes. That one key covers every recipe on this page. If you connected earlier with a narrower key, create a new one and set it as the POSTHOG_CLI_API_KEY environment variable with the Sensitive visibility.
Workflows that run on GitHub events, such as a push to
main, need your EAS project linked to a GitHub repository. See Get started with EAS Workflows.
Mark a deploy on your PostHog timeline
Send a PostHog event every time you publish an update. Each deploy then shows up in your PostHog data, so you can check whether a change in a metric lines up with a release.
How it works
- The
updatejob publishes an EAS Update to themainbranch. eas/posthog_capture_eventruns after it and records the deploy with the standard EAS properties.eas/account,eas/project_id, andeas/workflow_idcome from the always-available workflow contexts;eas/update_idandeas/runtime_versioncome from the update job's outputs. With thefingerprintruntime version policy, the runtime version changes whenever your native runtime does, based on your project's fingerprint.- Because no
distinct_idis set, the event is anonymous and does not create a person profile.
Annotate a release
Create a PostHog annotation when you publish an update. Annotations appear as markers on every chart in the project, so you can see each release directly on the graphs it affects.
How it works
- The
updatejob publishes the update. eas/posthog_annotationpins the annotation to the current time, and the runtime version in the content identifies the release.
Upload source maps for error tracking
Upload your source maps so error tracking can symbolicate crashes back to your original code. How they get uploaded depends on the pre-packaged job that produced the bundle. Both paths need the PostHog setup from the Using PostHog guide.
A build uploads source maps on its own. The posthog-react-native/expo config plugin uploads them during the native build, so the job needs nothing extra:
An update ships JavaScript only, so you upload its source maps after publishing. The update job can't run an extra step, and the source maps have to be on disk in the job that uploads them. So publish with eas update for one platform in a custom job and add the upload step there:
How it works
eas update --platform iospublishes the update and leaves the export, source maps included, in the dist directory. Export one native platform per job: a full export also includes a web bundle, which PostHog's Hermes upload rejects.eas/posthog_upload_sourcemapsuploads those maps to PostHog error tracking, so stack traces from the update symbolicate back to your original code. It uses the PostHog Metro config, which gives each bundle the chunk IDs that match it to its source maps. See error tracking for how symbolication works.
Release a feature behind a flag
Keep a feature's rollout state in a file in your repo and let the workflow apply it to PostHog. Shipping a feature or widening its rollout becomes a pull request that edits one file.
How it works
- The
pathsfilter runs this workflow only when .eas/feature-rollout.json changes, so the file is where you define the rollout state. read_rolloutreads the file withset-outputand exposes the values through the job'soutputs.eas/posthog_flag_rolloutapplies the flag state after both dependencies finish, so the flag flips only once the code that reads it is live.
Roll out or roll back on your error rate
Release to a small percentage of users, watch the error rate, and let the workflow decide. When errors stay low, it widens the flag to everyone. When they don't, it turns the flag off. Either way, nobody has to watch a dashboard in between.
How it works
- The
updatejob ships the code, theneas/posthog_flag_rolloutexposes it to 25% of users. eas/posthog_wait_for_metricruns a HogQL query and waits until the exception count over the last 30 minutes is 5 or fewer. Once the gate clears,full_rolloutwidens the flag to 100%. If a bad rollout keeps the count elevated, the gate times out and fails, andfull_rolloutnever runs.- On failure,
roll_backturns the flag off. It usesafterwithif: ${{ failure() }}because aneedsdependency would not work here. When aneedsdependency fails, the job is skipped before itsifcondition is evaluated.failure()reflects the whole workflow run, so keep this workflow focused on the rollout and its gate.
Roll out an EAS Update channel, or revert on failure
The rollout above controls exposure with a PostHog feature flag. If you roll out with EAS Update channel percentages instead, the same gate drives eas channel:rollout: publish an update, send a fraction of users to it, watch the error rate, then widen the rollout to everyone or revert it. It touches more of EAS than any other recipe here: the update job, channel rollouts, the metric gate, and an outcome event.
How it works
- The
updatejob publishes to therolloutbranch, theneas channel:rolloutstarts a rollout on theproductionchannel that sends 10% of users to the new update. The--runtime-versionvalue must match the published update's runtime version. eas/posthog_wait_for_metricholds until the exception count over the last 15 minutes stays under 10, then records the count that cleared the gate through the step'svalueoutput.- When the gate clears,
widenends the rollout with--outcome republish-and-revert, which publishes the new update to everyone. - When the gate fails,
revertends the rollout with--outcome revert, which sends users back to the previous update. It uses the sameafterplusif: ${{ failure() }}pattern as the flag rollback.
Take a flag to full rollout with human approval
Pause the workflow for an explicit approval in the EAS dashboard before a flag goes to 100%. Use this for rollouts you don't want to automate end to end.
How it works
- The
require-approvaljob pauses the workflow until a person approves or rejects it in the EAS dashboard. - If approved,
eas/posthog_flag_rolloutrolls the flag out to 100%. If rejected, the job fails andgo_fullis skipped. - The follow-up event records when the flag reached full rollout.
Turn a feature off with a kill switch
Turn a feature off for everyone with a single command. This workflow has no on trigger, so it runs only when you start it, such as during an incident.
How it works
-
Run it with:
Terminal -
eas/posthog_flag_rolloutturns the flag off, and the follow-up event records that it happened.
Turn a feature off automatically on an error spike
This is the kill switch from the previous recipe wired to an error gate, so it turns the feature off on its own when the error rate spikes after a deploy.
How it works
- The
updatejob publishes the update, andeas/posthog_wait_for_metricwaits until the exception count over the last 10 minutes is under 20. If a bad update keeps the count elevated, the gate times out and fails. - The
auto_kill_switchjob uses the sameafterplusif: ${{ failure() }}pattern as the flag rollback. eas/posthog_flag_rolloutturns the flag off, the same action as the manual kill switch, and records that it fired automatically.
Announce a release once users adopt it
Post a Slack message once enough users are on the new update. The workflow waits for real adoption instead of announcing as soon as the deploy finishes.
How it works
- The
updatejob publishes the update. eas/posthog_wait_for_metricwaits until at least 50 distinct users have sent events in the last hour.- The
slackjob announces the release once the gate clears. SetSLACK_WEBHOOK_URLas an environment variable on EAS.
More recipes
Shorter recipes for narrower needs.
Wait for a specific event
Hold the workflow until a specific event arrives, such as a post-deploy smoke test reporting success, instead of gating on an aggregate metric.
eas/posthog_wait_for_query clears once the query returns true. The 30-minute window keeps an old smoke-test event from clearing the gate immediately.
Check your nightly error budget
Run a gate on a schedule instead of after a deploy, as a standalone health check.
The on.schedule.cron trigger runs this independently of any deploy, so it works as a standing health check rather than a gate on a specific rollout.
Mark a store submission
eas/posthog_capture_event works the same way for native releases as it does for updates.
Important notes
- Credentials come from the connect command. Every function defaults to the environment variables that
eas integrations:posthog:connectsets, and each accepts anapi_keyinput when you need to override them. To set them yourself, add these as EAS environment variables:EXPO_PUBLIC_POSTHOG_API_KEYforeas/posthog_capture_event, andPOSTHOG_CLI_API_KEYplusPOSTHOG_CLI_PROJECT_IDfor every other function. The syntax reference lists the scope each function requires. - A gate that times out fails the step.
eas/posthog_wait_for_metricandeas/posthog_wait_for_queryhave noignore_errorinput, so a gate that never passes stops the rollout instead of letting it continue. - Gates pass as soon as the condition holds. A gate checking for a low error count can pass on its first check, including right after a deploy, before users are on the new update. Pair it with an adoption gate, like the one in Announce a release once users adopt it, when you need errors to have had a chance to surface first.
- Scope queries to the current run. Event and metric queries return historical data. Add a
timestamp > now() - INTERVAL ...filter, or a property unique to this run, so an old event does not clear a gate immediately. Setting up release tagging attacheseas/update_id,eas/channel, andeas/runtime_versionto every client event, so you can filter a gate onproperties['eas/update_id']to scope it to the exact release.