Appearance
GitHub Actions Integration (Beta)
tailor setup generates GitHub repository automation for your Tailor Platform application, including deploy workflows, a workflow that commits generated files to pull requests, and Renovate configuration.
Beta: This command is under active development. CLI flags, the generated workflow, and the
.github/tailor.lockschema may change before general availability.
Installation
The setup commands are provided by the @tailor-platform/sdk-plugin-setup CLI plugin. Install it next to the SDK:
bash
npm install -D @tailor-platform/sdk-plugin-setupThe Tailor CLI discovers the plugin automatically from node_modules/.bin (or your PATH). Run tailor plugin list to confirm it resolves.
Quick start
Run the command from the root of your SDK project (where tailor.config.ts lives):
bash
# Branch target: deploy to stg on every push to main
tailor setup ci branch --name my-app-stg
# Tag target: deploy to production when a tag is pushed, with an approval gate
tailor setup ci tag --name my-app-prod \
--branch main --environment productionAfter running the command, follow the Next steps printed to the terminal: run tailor setup ci env to get the commands that set the secrets and variables each GitHub Environment needs, and tailor setup ci env --check to confirm none is missing (see Setting secrets and variables), then commit the generated files.
The generated workflow deploys to whichever workspace its TAILOR_PLATFORM_WORKSPACE_ID Environment variable points at — it never creates or renames a workspace. Provision the workspace and set that variable before the first deploy (see Targeting a workspace).
Targets
A target is one workflow file that handles one deployment destination. Run setup once per target.
Branch target (recommended for staging)
The branch target fires on pull requests and pushes to the branch you specify (defaulting to the repository's default branch when --target is omitted):
bash
tailor setup ci branch --name my-app-stg
# Equivalent to:
tailor setup ci branch --name my-app-stg --target mainWhat it does:
- On pull request: runs
generate, checks that generated files are committed (generate-check), and posts a deployment plan as a PR comment. (The plan and deploy jobs are independent; pull requests run plan only.) - On push to the branch: deploys. The deploy action runs
generateand applies the config; it does not re-run the PR plan. - On
workflow_dispatchwithdry-run: true: runs plan only (useful for rollback verification — see Rollback). Withdry-run: false(default) it deploys the selected branch, like a push. Pass--restrict-dispatchto deploy only the target branch this way (see Restricting manual deploys).
Fork pull requests cannot read repository secrets. For forks, the plan step is automatically skipped; generate-check and other non-secret checks still run.
ERD preview artifacts
Pass --erd-preview on a branch target to add TailorDB ERD preview artifacts to pull requests:
bash
tailor setup ci branch --name my-app-stg --erd-previewThe generated workflow runs tailor tailordb erd, which is provided by the @tailor-platform/sdk-plugin-tailordb-erd CLI plugin — install it as a dev-dependency in your project:
bash
npm install -D @tailor-platform/sdk-plugin-tailordb-erdThe generated workflow builds one self-contained ERD viewer HTML file for each owned TailorDB namespace in tailor.config.ts. The viewer compares the pull request merge result with the base branch, can switch between the current schema and highlighted diff, uploads the HTML files as unarchived Actions artifacts, and upserts a PR comment with artifact links. On GitHub Enterprise Server, also pass --ghes (see GitHub Enterprise Server).
ERD preview does not use Tailor Platform credentials. Fork pull requests still build artifacts, but the comment step is skipped because fork tokens cannot write PR comments.
--erd-preview is only available for branch targets. The namespace list is recorded in .github/tailor.lock; the pull request workflow compares the head and base lock files so newly added or removed namespaces can still produce all-added or all-removed viewer artifacts. Re-run setup ci branch after adding or removing TailorDB namespaces. setup check reports drift when the recorded ERD preview namespaces no longer match the current config.
Migration test on a labeled pull request
Pass --migration-test on a branch target to add a job that runs tailor tailordb migration test against a pull request on demand. It is opt-in and requires a config with TailorDB migrations.
bash
tailor setup ci branch --name my-app-stg --migration-testThe job runs only when someone adds the tailor:migration-test label to a pull request from the same repository (change the label with --migration-test-label). It runs the pending migrations in a temporary workspace created next to the source workspace, writes the --json result to the job summary, and removes the label as its last step, whether the test succeeded, failed, or was cancelled while running. To run it again, add the label again. If a run is cancelled before the job starts (for example, a reviewer rejects the environment's approval), the label stays on the pull request: remove it and add it again. Anyone who can label pull requests in your repository can start the job, and each run creates and deletes a workspace. Runs for one pull request never cancel each other.
The generated workflow also lists labeled in its pull_request types. The plan and ERD preview jobs skip label events, so adding an unrelated label does not re-run them.
Runs started by a label report their result job as tailor-result (<name>) [label] instead of tailor-result (<name>). A label event therefore never replaces the result of the check you require for branch protection. The migration test result appears only under the [label] name, so it does not block merging through the check you require.
The job clones the source workspace's TailorDB records and passes --clone-timeout 30m, which bounds only the wait for the data clone. If the clone takes longer, the command fails with the clone's operation ID in the error and removes its temporary workspace, while the clone itself keeps running on the platform.
The migration test step is limited to 50 minutes and the job to 60, so the label is still removed when the step times out. A step that is stopped at its limit (or a job cancelled at the job limit) gives the command no chance to clean up: delete the leftover temporary workspace with tailor workspace delete.
Choosing the source workspace
migration test reads the source workspace (it is not modified), builds a temporary workspace from it in the same organization and folder, and deletes that workspace afterwards. Because the machine user creates and deletes that workspace, it needs an editor or admin role on the organization or folder that holds the source workspace; a viewer role is not enough. The clone also needs the same role on the source side. The clone copies TailorDB records only: IdP users and file blobs are not copied.
Two setups are supported:
- Source is the plan/deploy workspace (default). The job uses the same GitHub Environment as the plan and deploy jobs, so it reads the same
TAILOR_PLATFORM_WORKSPACE_IDvariable and machine-user secrets. Nothing extra to configure. If that workspace holds too little data to be a useful test, point the job at a workspace that does with the next setup. - Source is a different workspace, such as production. Pass
--migration-test-environment <env>. The job then uses that GitHub Environment and reads the source workspace from itsTAILOR_PLATFORM_MIGRATION_TEST_SOURCE_WORKSPACE_IDvariable, together with the machine-user secrets set on that same environment. Give this environment a machine user whose role you are willing to hold on the source workspace's organization or folder, since it needs the editor-or-admin role described above. Pending migrations run from the source workspace's migration number up to the latest, so a source that is behind the deploy target replays more migrations, which is closer to what a production deploy will do.
A job can declare only one GitHub Environment, and a repository secret or variable is read from that environment, which is why a different source needs its own environment. tailor setup ci env prints what each environment needs, including the dedicated one. Keep the dedicated environment separate from your production deploy environment: a pull request job runs on the pull request merge ref (refs/pull/<number>/merge), so if an environment restricts which branches may deploy or requires reviewers, check how that affects the pull request job before pointing the migration test at it. A required-reviewers rule makes the job wait for approval before it can read the environment's secrets. The job runs the pull request's own code with that environment's credentials, so add one when the source workspace holds production data.
Fork pull requests cannot read secrets, so the job is skipped for them. --migration-test is not available together with several --dir values.
Tag target (recommended for production)
The tag target fires when a tag matching --tag-pattern (default v*) is pushed:
bash
tailor setup ci tag --name my-app-prod \
--tag-pattern "v*" --branch main --environment productionWhat it does:
tailor-tag-guard(generated when--branchis supplied): checks that the tagged commit is reachable frommain. A tag on an unrelated commit is silently skipped, not an error.tailor-plan: runsgenerate,generate-check, and posts a plan summary to the Actions step summary (no PR comment, because there is no PR).tailor-deploy: waits fortailor-plan, then deploys. If the target environment has required reviewers configured, GitHub requires their approval before the deploy job starts.- On
workflow_dispatch: thetailor-tag-guardresult is ignored — the plan job runs regardless of branch reachability (useful for rolling back to any tag).dry-run: truestops before the deploy job. With--restrict-dispatch, the deploy job runs only for a tag that passes the guard (see Restricting manual deploys).
Choosing --branch for the tag target
The branch each target reads plays a different role, under a different flag:
| Target | Flag | Role |
|---|---|---|
| Branch | --target | The branch that triggers the workflow (push + PR base). Defaults to the repo's default branch. |
| Tag | --branch | The branch whose history the tag must be reachable from. Omit to disable the guard entirely. |
The workspace name (--name, or the config name when omitted) must be 3–63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. It is used for the generated file name, the workflow name:, the plan label, and the default GitHub Environment name; it does not select which workspace gets deployed (see Targeting a workspace).
For a preview target, the name can be at most 50 characters: each pull request gets its own workspace named <name>-pr-<number>, which must fit in 63 characters.
Preview target (per-pull-request workspace)
The preview target deploys each pull request to its own workspace and deletes that workspace when the pull request is closed:
bash
tailor setup ci preview --name my-app --region asia-northeastFork pull requests are skipped. Every other pull request gets a preview, draft pull requests included. --require narrows that to one of:
--require readyskips draft pull requests. The preview is deployed when the draft is marked ready for review.--require labeldeploys only pull requests labeledtailor:preview. A labeled draft deploys too, because the label asks for the preview.
When tailor setup update regenerates a preview target generated before --require existed, it reads the target as --require ready, or as --require label if it required the label. The first keeps skipping drafts; the second now also deploys a labeled draft. To deploy drafts as well, run tailor setup ci preview again without --require.
Committing generated files to pull requests
Each deploy workflow checks that the files tailor generate writes, such as seed data and enum constants, are committed, and fails when they are out of date. To have CI commit them instead, generate the autofix workflow:
bash
tailor setup ci autofix # commit with the workflow token
tailor setup ci autofix --provider github-app # commit with your GitHub App
tailor setup ci autofix --dir apps/erp --dir apps/users # run tailor generate in each appIt writes .github/workflows/tailor-autofix.yml, which runs tailor generate on every pull request and, when that changes files, commits them to the pull request branch as chore: run tailor generate. Only the changes tailor generate makes are committed: files that earlier steps leave in the checkout, such as an .npmrc, are not. The deploy workflows keep their check, which fails on the commit that needed the fix and passes on the fix commit.
--provider chooses what makes the commit:
| Provider | Commits as | Checks on the fix commit | Setup |
|---|---|---|---|
github-token (default) | github-actions[bot], with the workflow token | Wait until someone with write access selects Approve workflows to run on the pull request | None |
github-app | Your GitHub App | Run automatically | Create a GitHub App with the Contents read and write permission, install it on the repository, and set TAILOR_AUTOFIX_APP_CLIENT_ID and TAILOR_AUTOFIX_APP_PRIVATE_KEY (see Setting secrets and variables) |
autofix-ci | The autofix.ci GitHub App | Run automatically | Install the autofix.ci App on the repository |
securefix | Your securefix server workflow | Run automatically | Set up the securefix server repository and its GitHub Apps, pass the server repository's name with --securefix-server-repository, and set the client App's TAILOR_AUTOFIX_APP_CLIENT_ID and TAILOR_AUTOFIX_APP_PRIVATE_KEY |
GitHub does not start workflows on its own for a commit pushed with the workflow token, so with github-token the pull request shows that its workflows are awaiting approval after each fix commit. Use another provider to have them run right away.
- Fork and Dependabot pull requests get no write token or secrets, so the autofix workflow skips them, except with
autofix-ci, whose App commits to fork pull requests. Their authors runtailor generateand commit the result as before. - A branch that moved on: if someone pushes to the branch while the workflow runs, it does not commit; the run for the new commit regenerates the files.
- Output that keeps changing: if
tailor generatechanges files again right after the fix commit, the workflow fails instead of committing again. Check that running it twice in a row changes nothing. - Approval rules: the fix commit becomes the most recent push to the pull request, and a bot pushed it. A branch protection rule that requires the most recent push to be approved by someone other than its pusher is then satisfied by an approval from anyone else, including whoever pushed the commit before it.
- Renovate stops updating a pull request that has commits from someone else. Add the fix commits' author email to
gitIgnoredAuthorsin your Renovate configuration, such as41898282+github-actions[bot]@users.noreply.github.comforgithub-token. - Secrets: with
github-app, every workflow in the repository can read the App's private key, which can write to the repository. Give the App only the Contents permission and install it only where it commits.securefixkeeps the key that writes in the server repository instead. - Dependabot stops rebasing a pull request that has commits from someone else, which happens when
autofix-cicommits to one of its pull requests. - With
autofix-ciandsecurefix, the workflow is namedautofix.ciandsecurefix, the names those services accept requests from by default. Withautofix-ci, it also gives git its read-only token aftertailor generatefinishes, because autofix.ci fetches the pull request head itself. - GitHub Enterprise Server is not supported.
To stop committing, delete the workflow with tailor setup delete .github/workflows/tailor-autofix.yml.
Targeting a workspace
The generated plan and deploy jobs target a workspace by id, read from the TAILOR_PLATFORM_WORKSPACE_ID GitHub Environment variable. They never resolve a workspace by name and never create one. Set this variable per environment before the first deploy.
Because the variable is scoped to a GitHub Environment, both the plan and deploy jobs declare environment: so the value resolves. When you omit --environment, the environment name defaults to the workspace name.
Provision the workspace (once per environment) and obtain its id. For now this is a manual step:
bashtailor workspace create # copy the printed workspace idSet the id as the Environment variable (the environment name is your
--environmentvalue, or the workspace name when omitted).tailor setup ci envprints this command together with the other secrets and variables each environment needs (see Setting secrets and variables):bashgh variable set TAILOR_PLATFORM_WORKSPACE_ID --env=my-app-stg
If TAILOR_PLATFORM_WORKSPACE_ID is unset, deploy fails because the target workspace is not provisioned, and plan reports that the workspace is not provisioned yet instead of running a dry-run.
If the target environment has required reviewers, that approval gate applies to the plan job as well as deploy, because plan must enter the environment to read the variable. (A token-based read that lets plan run without entering the environment is planned.)
Package manager
The generated workflow sets up your package manager, installs dependencies with it, and runs tailor through it. setup ci detects the package manager from the lockfile at the repository root (pnpm-lock.yaml, yarn.lock, bun.lock, bun.lockb, package-lock.json, or npm-shrinkwrap.json), or, when there is none, from the packageManager or devEngines.packageManager field of the root package.json. When neither names one, setup ci stops instead of guessing.
Pass --package-manager (pnpm, npm, yarn, or bun) to choose it yourself, for example to generate the workflow before the lockfile exists:
bash
tailor setup ci branch --name my-app-stg --package-manager pnpmThe workflow installs dependencies at the repository root, so commit the lockfile there before it runs. tailor setup update keeps a package manager chosen with --package-manager and detects it again otherwise.
Generated files
Running a workflow setup subcommand creates or updates:
.github/workflows/tailor-<workspace-name>.yml
The workflow file. The name: field is set to Tailor (<workspace-name>) so you can distinguish multiple workspaces in the Actions UI.
Every checkout in the workflow sets persist-credentials: false, so the GitHub token is not left in .git/config while the job installs and runs your project's code. The tag guard step passes the job token only to its git fetch, and the plan step passes it to its git fetch and to the step that posts the plan comment on the pull request; neither writes it to .git/config. If installing your dependencies fetches git repositories that require authentication, configure the credentials in your own step.
See Customizing the generated workflow for what you can edit.
.github/workflows/tailor-autofix.yml
The workflow setup ci autofix generates (see Committing generated files to pull requests). You can customize it the same way.
Customizing the generated workflow
The SDK owns the jobs and steps whose id starts with tailor-, and the top-level name:, on:, and permissions:. Do not edit or rename them. Everything else is yours, and re-running setup keeps it:
- Your own jobs and steps. Add them anywhere, with an
id(if any) that does not start withtailor-: the prefix is reserved for the SDK, even in a job of your own. A step you add inside a managed job stays right after the managed step it followed. For example, add private registry authentication or a system dependency before the managedtailor-setupstep, and post-install extras (such asplaywright install) after it. A job of your own can depend on a managed job withneeds: tailor-deploy. - Your own top-level keys, such as a workflow-level
env:ordefaults:. - The top-level
concurrency:of a preview workflow. The SDK writes a default, and re-runningsetupkeeps your edits to it. If you delete it, re-runningsetupadds the default back. - Runtime settings of managed jobs:
runs-on,timeout-minutes,container, andenv. If every managed job that re-runningsetupkeeps uses the sameruns-onyou set, such as self-hosted runners, a managed job it adds (for example, the ERD preview jobs when you add--erd-preview) uses it too; otherwise, set the new job'sruns-onyourself. On a managed job generated without anenvironment:(such astailor-tag-guardortailor-erd-preview), you can also add one, for example so a step you added there can read that GitHub Environment's secrets. Theenvironment:oftailor-plan,tailor-deploy, and the preview jobs comes from--environment, so change it with that option instead. - These inputs of managed steps:
ignoreontailor-generate-checkandtailor-drift-check,fail-on-driftontailor-drift-check,install-commandontailor-install,node-version-fileontailor-setup,labelontailor-plan,ttlontailor-preview-deploy, anduser-mappingontailor-notify. - Your own jobs in the
needsoftailor-resultandtailor-preview-result, so they count toward the workflow's result (see Requiring the workflow in branch protection).
In a preview workflow, the tailor-preview-deploy job exposes the per-PR workspace as the outputs workspace-id, workspace-name, and app-url, so a job of your own can run tests or deploy extra assets against it:
yaml
deploy-assets:
needs: tailor-preview-deploy
runs-on: ubuntu-latest
environment: my-app
env:
TAILOR_PLATFORM_WORKSPACE_ID: ${{ needs.tailor-preview-deploy.outputs.workspace-id }}
TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID: ${{ vars.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID || secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}
TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}
steps:
# checkout, dependency installation, and your tailor commandsA job that runs tailor against the workspace needs the machine-user secrets, so it declares the preview target's GitHub Environment (the --environment value, or the workspace name when omitted) as above. A job that only uses app-url, such as end-to-end tests, does not.
The job is skipped whenever tailor-preview-deploy is skipped: when a pull request is closed, for fork pull requests, for draft pull requests with --require ready, and for unlabeled ones with --require label.
A preview workspace expires 7 days (ttl: 7d on tailor-preview-deploy) after the last push to its pull request. When a pull request is closed, tailor-preview-cleanup deletes its workspace and also deletes the other expired preview workspaces of the same workspace name in the folder (TAILOR_PLATFORM_FOLDER_ID, or directly under the organization when unset), so a workspace whose own cleanup never ran does not stay forever. A pull request that stays idle past the ttl can have its workspace deleted while it is still open; the next push creates a fresh one. Change ttl to keep previews longer or shorter. Workflows generated before this was added get these settings the next time you run tailor setup update.
Each new preview workflow run for a pull request, such as after a push or when the pull request is closed, cancels the run still in progress for it, including your jobs, so they do not keep running against a workspace that the new run may redeploy or delete. Events on a pull request that stays closed, such as a label added after merging, do not cancel anything. A job with if: always() still runs when its run is cancelled, and the new run waits for it to finish; use if: ${{ !cancelled() }} instead so it stops with the run.
Likewise, the tailor-deploy job of a branch or tag workflow exposes the deployed workspace as the outputs workspace-id and app-url to a job with needs: tailor-deploy. Reading them does not require the target's GitHub Environment; a job that runs tailor against the workspace declares it for the machine-user secrets, as in the preview example above.
Comments above your own jobs and steps and at the end of the file are kept too. Comments inside managed jobs and steps, and edits to the header comment, are not kept.
setup check reports an edit to a managed part, and re-running setup stops on it; both name the edited job, step, or top-level key. Revert the edit, or pass --force to reset the managed parts to the current template; --force still keeps your own jobs, steps, and settings. A managed step you renamed to an id without the tailor- prefix counts as your own, so --force adds the managed one back next to it; rename it back instead of forcing. A managed job you renamed still holds the SDK's tailor- steps, so setup check reports them as reserved ids and setup stops on them, even with --force; rename the job back. To start over from a clean template, delete the file and re-run setup.
When a template update removes a managed job that contains steps of yours, or a job of yours needs a removed job, setup stops and names them. Move those steps into a job of your own (or update the needs) and re-run. --force drops steps left inside a removed job.
setup check reports a job or step of yours whose id starts with tailor-, and re-running setup stops on it, naming the id. Rename it; --force does not rename or remove it.
Files generated by an older SDK version are compared as a whole, so the first re-run after upgrading stops if you edited the file in any way. Re-run once with --force to switch it to this model; your own jobs and steps are kept.
Requiring the workflow in branch protection
Branch and preview workflows end with a job that reports the result of the whole workflow: tailor-result in a branch workflow and tailor-preview-result in a preview workflow. Its check is named after the workspace, such as tailor-result (my-app), so each workflow in the repository has its own. Require that one check in branch protection instead of listing the jobs one by one.
The job runs after every other SDK-managed job of the workflow, even when one fails or the run is cancelled. It fails when a job in its needs failed or was cancelled, and passes when they succeeded or were skipped, for example when the preview deploy is skipped for a fork pull request. When a newer run cancels a job of an older one, such as the preview deploy of a commit you pushed over, the older run's check fails; the newer run reports its own.
To make jobs of your own count, add them to its needs. A matrix job is listed once by its job id, however many combinations it expands to:
yaml
tailor-preview-result:
name: tailor-preview-result (my-app)
needs:
- tailor-preview-deploy
- tailor-preview-cleanup
- seed
- e2eRe-running setup keeps the jobs you add there and updates the tailor- entries to the jobs the SDK generates. A job skipped because a job it needs failed also counts as passing, so list every job of yours whose failure should block merging, not only the last one: above, seed is listed because e2e needs it.
With --require label, the preview workflow does not start when a pull request is opened, so its check is not reported until the pull request is labeled, reopened, or pushed to. With --require ready, a draft pull request skips the preview deploy and its check passes; marking it ready for review starts the workflow, so the deploy and its check follow right away. Tag workflows have no such job: they run on tags, not on pull requests.
.github/tailor.lock
A machine-owned JSON file that tracks which files the SDK manages, the inputs they were generated from, and their content hashes, plus the id of every app in the repository (see App id). Commit this file. The SDK uses it to recognize its own files on re-runs and to detect hand edits. The only part meant for hand editing is appIds, and only to re-key an entry after moving an app directory or to delete the entry of a removed app.
App id
Every application has a stable id (a UUID) that the SDK uses to recognize the resources it owns across renames. In a repository set up with tailor setup, the id lives in .github/tailor.lock under appIds, keyed by the config file's repository-relative path:
jsonc
{
"version": 2,
"targets": [/* generated workflows */],
"appIds": {
"apps/order/tailor.config.ts": "d0a3398a-…",
"apps/billing/tailor.config.ts": "7f21c4e0-…",
},
}setup records the id when it generates a workflow, and a local tailor deploy records one for any config that has none yet. Because the key is the config path, renaming the app keeps its id, and copying a config to a new directory gives the copy a fresh id instead of the original's. If tailor.config.ts still has an id field from before, setup or a local deploy moves it into the lock and removes it from the config; a config id that disagrees with the lock stops the command so you can decide which value to keep.
In CI, tailor deploy never assigns an id — if one were assigned fresh on each run, every deploy would create a brand-new application and lose ownership of previously deployed resources. A config without a recorded id fails the plan job with instructions to run tailor deploy locally and commit the lock. If your pipeline intentionally deploys a fresh, throwaway application on every run (for example an end-to-end test harness that creates and deletes its own workspace), set TAILOR_CI_ALLOW_ID_INJECTION=true to let CI assign one.
When you move an app directory, its appIds entry still points at the old path. Locally, deploy and setup notice the single unmatched entry and ask whether the app was moved; answering yes re-keys the entry so the app keeps its id. In CI, or when more than one entry is unmatched, the command stops and asks you to re-key the entry (or delete entries of removed apps) by hand. Re-run the relevant setup subcommand as well so the workflow's paths follow the move.
Secrets
The generated workflow requires one secret (the optional Slack token is listed in Setting secrets and variables):
| Secret | Description |
|---|---|
TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET | Machine user client secret |
The machine user client ID is a variable, not a secret: it cannot authenticate without the client secret. The generated workflow reads vars.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID and falls back to secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID, so a repository that already stores the client ID as a secret keeps working unchanged. Storing the client ID as a secret is deprecated, and the fallback will be removed in a future release, so move it to a variable (gh variable set TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID) and delete the secret.
Set the client secret as a secret and the client ID as a variable on the target GitHub Environment (the --environment value, or the workspace name when omitted); tailor setup ci env prints the commands (see Setting secrets and variables).
Setting them at the environment level isolates each target's credentials and keeps them alongside that environment's TAILOR_PLATFORM_WORKSPACE_ID variable. You can also set the client secret as a repository-level secret and the client ID as a repository-level variable if every target shares one machine user, but then any workflow on any branch can read them, so the environment's protection rules no longer guard your deploys.
Setting secrets and variables
tailor setup ci env reads .github/tailor.lock and prints, for every GitHub Environment the generated workflows use, the commands that create the environment (only when it does not exist yet) and set its secrets and variables, followed by the repository secrets and variables the autofix workflow needs. It is read-only, so re-run it whenever you add a target. When the origin remote points at github.com, the output names that repository; otherwise it names the repository gh repo view reports for the current directory, with its host on GitHub Enterprise Server. Either way the commands work from any directory. When neither names a repository, gh resolves it from the current directory when you run the commands, and the Terraform output leaves it as a placeholder.
bash
tailor setup ci env # gh CLI commands (default)
tailor setup ci env --format terraform # Terraform for the integrations/github provider
tailor setup ci env --environment my-app-stg # only one environment (repeat for several)
tailor setup ci env --check # check what is registered in GitHub (see below)The list follows what each generated workflow actually reads:
| Name | Kind | Targets | Required | Where the value comes from |
|---|---|---|---|---|
TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID | variable | all | yes | Client ID of the platform machine user CI signs in as (an existing secret of the same name still works as a deprecated fallback); it needs an editor or admin role on the organization or folder that holds the workspace. An organization or folder admin creates one in the Tailor Console and grants it a role; without the required permission you cannot view or create machine users |
TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET | secret | all | yes | Client secret of the same platform machine user |
TAILOR_PLATFORM_WORKSPACE_ID | variable | branch, tag | yes | id printed by tailor workspace create, or listed by tailor workspace list |
TAILOR_PLATFORM_MIGRATION_TEST_SOURCE_WORKSPACE_ID | variable | branch (migration test environment) | yes | ID of the workspace the migration test starts from, set on the environment passed to --migration-test-environment; it is only read to build the temporary workspace. Needs the machine user's editor or admin role on that workspace's organization or folder |
TAILOR_PLATFORM_ORGANIZATION_ID | variable | preview | yes | Organization to create the per-PR workspaces in (a machine user cannot create a workspace without one): organizationId listed by tailor organization list |
TAILOR_PLATFORM_FOLDER_ID | variable | preview | no | Folder to create the per-PR workspaces in: id listed by tailor organization folder list -o <organization id>. When unset they go directly under the organization, which needs the machine user's role on the organization itself |
TAILOR_PLATFORM_FAIL_ON_DRIFT | variable | all | no | true to fail the drift check when it finds drift |
TAILOR_SLACK_BOT_TOKEN | secret | branch, tag | no | Bot User OAuth Token (xoxb-...) of a Slack app with the chat:write scope |
TAILOR_SLACK_CHANNEL_ID | variable | branch, tag | no | Channel ID (C...) from the channel details in Slack; invite the bot to the channel |
TAILOR_SLACK_USER_MAPPING | variable | branch, tag | no | JSON object mapping GitHub usernames to Slack member IDs (for example {"alice":"U0123456"}) so notifications mention the actor; read only after you uncomment the user-mapping input of the tailor-notify step |
TAILOR_AUTOFIX_APP_CLIENT_ID | variable | autofix (github-app, securefix) | yes | Client ID of the GitHub App that commits for the autofix workflow (with securefix, the securefix client App), from the App's settings page |
TAILOR_AUTOFIX_APP_PRIVATE_KEY | secret | autofix (github-app, securefix) | yes | Private key of the same App, generated on its settings page; pass the .pem file on standard input (gh secret set TAILOR_AUTOFIX_APP_PRIVATE_KEY < key.pem) |
The autofix workflow uses no GitHub Environment, so its two entries are set on the repository; the output lists them under the repository instead of an environment.
See Account management for how organizations, folders, workspaces, and machine users relate.
Set TAILOR_SLACK_BOT_TOKEN and TAILOR_SLACK_CHANNEL_ID together to enable Slack deploy notifications.
The gh output creates an environment only when GitHub reports it missing and leaves optional entries commented out. Run the commands one at a time: each gh secret set / gh variable set prompts for its value.
The Terraform output takes every value from an input variable (secrets are sensitive) and creates an optional entry only when its variable is set, so no value is written to the output. Terraform still stores the secret values in its state in plain text, so keep the state encrypted and access-restricted, or set the secrets with the gh output instead. Its header lists the steps with the variable names for your environments: authenticate the provider, put non-secret values in terraform.tfvars, pass secrets as TF_VAR_<name> environment variables, and import each environment and variable that already exists (for example terraform import github_repository_environment.production my-repo:production) before terraform apply: creating a variable that already exists fails, while secrets are overwritten. The generated environments ignore changes to their protection settings (reviewers, wait timer, branch policy), so importing an environment keeps the approval gate you configured; remove the lifecycle block to manage those settings in Terraform instead.
Checking what is registered
tailor setup ci env --check reads GitHub with gh and reports, for each environment and for the repository entries of the autofix workflow, which of these entries are registered. It fails when a required entry is missing, reports missing optional entries without failing, and prints the gh commands that set only the missing entries, with where each value comes from. It changes nothing, and it checks names only, since GitHub never returns secret values.
- An entry set on the repository, or on the organization and shared with the repository, counts as registered, because the workflow reads it from there when the environment does not set it. The report says where each entry was found.
- An entry registered as the other kind, such as
TAILOR_PLATFORM_WORKSPACE_IDset as a secret, is reported as missing, because the workflow does not read it.TAILOR_PLATFORM_MACHINE_USER_CLIENT_IDset as a secret still counts, with a reminder to move it to a variable. ghmust be installed and logged in (gh auth login) as an account with write access to the repository, which listing secrets and variables requires. GitHub answers 404 for a private repository the account cannot see, so the check reports that as an access problem, not as missing entries.
--environment narrows the check the same way, leaving out the repository entries. --check cannot be combined with --format terraform.
GitHub Environments (approval gate)
Both the plan and deploy jobs are associated with a GitHub Environment — the --environment value, or the workspace name when omitted. The environment holds that target's TAILOR_PLATFORM_WORKSPACE_ID variable and machine-user secrets, and lets you:
- Require reviewer approval before the jobs run (suitable for production).
- Scope secrets and the workspace-id variable to specific environments so staging and production deploy to separate workspaces with separate machine users.
To configure the environment, go to your repository's Settings → Environments and create an environment whose name matches the target's environment. Add required reviewers, the TAILOR_PLATFORM_WORKSPACE_ID variable, and the environment-scoped secrets there.
Required reviewers gate the plan job as well, because plan must enter the environment to read TAILOR_PLATFORM_WORKSPACE_ID. (A token-based read that lets plan run without entering the environment is planned.)
Manual runs and dry-run
You can trigger the workflow manually from Actions → Run workflow. The dry-run input (boolean, default false) runs the plan job without deploying. Use this to preview what would change before executing a rollback or an out-of-band deploy. With dry-run off, a branch-target dispatch goes straight to deploy (like a push), while a tag-target dispatch runs plan first and then deploys.
By default you can select any branch or tag when dispatching manually, and it deploys that ref. For tag targets, the tag-guard check is skipped for manual dispatches, so you can deploy any commit regardless of branch membership.
Restricting manual deploys
Pass --restrict-dispatch to setup ci branch or setup ci tag to limit what a manual dispatch with dry-run off can deploy:
- Branch target: only the target branch. Dispatching another branch skips the deploy job.
- Tag target: only a tag. With
--branch, the tag must also pass the reachability guard. The tag does not have to match--tag-pattern.
Dry runs stay available from any ref. Re-run setup with the flag each time you regenerate the workflow; it is not carried over from the previous run.
The flag guards against deploying the wrong ref by mistake. It is not an access control: a manual dispatch runs the workflow file from the selected ref, so anyone who can push a branch can remove the check there. To enforce which refs can deploy, configure Deployment branches and tags on the target's GitHub Environment and keep the machine-user secrets on that environment. Because the plan job also enters the environment, allow refs/pull/*/merge as well so pull request plans keep running, and note that dry runs from other branches are then rejected too.
Seeding the workspace after deploy
Pass --seed to setup ci branch, setup ci tag, or setup ci preview to seed the workspace with tailor seed apply right after each deploy. Add --seed-dataset <name> (repeatable, implies --seed) to apply datasets on top of the base seed data:
bash
tailor setup ci branch --seed-dataset stg
tailor setup ci preview --region us-west --seed-dataset previewThe deploy job gains a tailor-seed step after the deploy step. Branch targets validate seed data before deploy and also in the PR or dry-run plan job; tag targets validate it in the plan job before deploy. Both use tailor seed validate --upsert with the selected datasets to check the id and required fields that seed apply --upsert needs. Preview targets validate and apply the data after deploy.
| Target | Seeds with | Effect |
|---|---|---|
| branch, tag | tailor seed apply --upsert | The fields of each committed seed row are written; other rows are kept |
| preview | tailor seed apply --truncate --yes | The preview workspace is emptied, IdP users included, and seeded from scratch |
The seed steps read the optional SEED_USER_PASSWORD secret of the GitHub Environment and pass it to tailor seed, which uses it as the password of _User rows that have none. Set it with tailor setup ci env. Validation on pull requests gets a fixed placeholder instead of the secret, since it only checks that a password exists.
Setup stops unless the config uses seedPlugin with machineUserName, which names the machine user that seeds, and an existing <distPath>/data/ directory. Every --seed-dataset must name an entry of the seedPlugin datasets option or a directory under <distPath>/data/. Commit the dataset directories the workflow seeds: a gitignored dataset such as a per-developer local/ is not in the CI checkout. Install @tailor-platform/sdk-plugin-seed so the tailor seed commands resolve in CI. For branch and tag targets, use a version that supports tailor seed validate --upsert; update the setup and seed plugins together. Give every TailorDB seed row an id and every field its table requires: --upsert matches rows by id, so run tailor seed fill and commit the result. Seeding is available for a target that deploys one app.
Before enabling it, note that:
- Every branch or tag deploy writes the fields of the committed seed rows over the workspace,
_Userrows and their passwords included, so an edit to one of those fields in the workspace is undone. Each row is updated like any other update: update hooks and validation run,updatedAtchanges, and executors withrecordUpdatedTriggerfire once per seeded row on every deploy. - Every preview deploy deletes the data reviewers added to the preview workspace. That is every push, and with
--require labelalso every label added to the pull request, since each one redeploys the preview. - A seed failure fails the deploy job and its notification, although the deploy itself went through.
- The step shares the deploy job's timeout. IdP users take about a second each.
- Steps you added after the deploy step stay right after it, so they run before
tailor-seed. Move a step belowtailor-seedwhen it needs the seeded data, and move it again if you regenerate without--seedand then with it. tailor-seedis an SDK-managed step. To seed another way (a different mode,--skip-idp, a condition), leave--seedoff and add your own step.
Re-run setup ci with the flags each time you regenerate the workflow; setup update keeps them.
Monorepo setup
For a monorepo where your SDK app lives in a subdirectory, pass --dir:
bash
tailor setup ci branch --name my-app --dir apps/backendThe working-directory for SDK commands is set accordingly, and the workflow only plans and deploys when that subdirectory or the generated workflow file changes. The workflow itself starts on every pull request and push: a tailor-changes job checks whether the change touches apps/backend/** or the generated workflow file itself, and the plan, deploy, and ERD preview jobs are skipped when it does not. A skipped job reports success, so you can require the workflow's result check in branch protection; a workflow that a paths trigger filter never started would leave it pending instead. If the tailor-changes job itself fails (for example, on a GitHub API error), the plan, deploy, and ERD preview jobs fail too instead of being skipped, so a required check blocks merging and nothing is deployed. Re-run the failed jobs once the cause is gone.
Deploying several apps together
To deploy several apps to the same workspace from one workflow, repeat --dir and pass --name for the workflow:
bash
tailor setup ci branch --name erp --dir apps/erp/backend --dir apps/users/backendsetup ci branch, setup ci tag, and setup ci preview accept repeated --dir. The workflow plans and deploys every app's tailor.config.ts in one multi-config deploy from the repository root, so an app can reference resources of another app with external: true. Add @tailor-platform/sdk to the root package.json so the tailor CLI resolves there; setup stops until it is declared. The generate check runs for each app directory, as do seed validation and the migration drift check on branch and tag workflows, and a change under any app directory runs the workflow's jobs.
With --erd-preview, each TailorDB namespace is previewed from the app that owns it. A namespace may be owned by only one app; the others reference it with external: true.
With more than one app, the preview comment does not link an application URL.
Running on changes outside the app directories
When the apps depend on code outside their directories, such as a frontend or shared packages, add those paths with --paths on setup ci branch or setup ci preview. Repeat it for each pattern:
bash
tailor setup ci preview --name erp --region asia-northeast \
--dir apps/erp/backend --dir apps/users/backend \
--paths "apps/*/frontend/**" --paths "modules/**" --paths pnpm-lock.yamlThe patterns are checked after the app directories and the generated workflow file, in order, and the last pattern that matches a changed file decides whether it counts. A change to the workflow file alone runs the generated plan, deploy, and preview jobs, and with them any job of yours that needs them, in the pull request or push that makes the change; to skip the jobs for such a change, exclude the file, for example --paths '!.github/workflows/tailor-erp.yml' (tailor-erp-preview.yml for the preview workflow). * matches within one path segment, ** matches any number of segments, and a pattern starting with ! excludes matching paths, so it can also exclude files inside an app directory, for example --paths '!apps/erp/backend/**/*.md'. Other glob characters (?, +, [ ], { }, ( ), and \) are not supported, and setup ci rejects a pattern that contains them. An app at the repository root already runs on every change, so --paths is not accepted with --dir ..
Rollback
tailor deploy is declarative: redeploying a past configuration returns the platform to that state. The recommended rollback approaches are:
Option 1 — Revert the commit (branch target)
bash
git revert <commit-sha>
git pushThe push triggers the deploy job, which applies the reverted configuration. To preview the diff first, open the revert as a pull request (the plan job comments the diff) or use a dry-run: true manual dispatch before merging.
Option 2 — Advance the tag (tag target)
Move the production tag to an earlier commit:
bash
git tag -f v1.2.3 <earlier-commit-sha>
git push --force-with-lease origin v1.2.3Or create a new tag that points to the earlier commit:
bash
git tag v1.2.4 <earlier-commit-sha>
git push origin v1.2.4Option 3 — Manual dispatch with dry-run verification
- Go to Actions →
Tailor (<workspace-name>)→ Run workflow. - Enter the tag or ref you want to redeploy.
- Set
dry-runtotrueand run. Inspect the plan output. - Run again with
dry-runset tofalse.
For tag targets, the tag-guard step is bypassed on manual dispatch, so you can dispatch from any ref. Environment approval (if configured) applies as usual. With --restrict-dispatch, step 4 deploys only a tag that passes the guard, and a branch target deploys only its target branch, so use Option 1 there.
Rollback limitations
- Schema and data are not rolled back. If the older config expects a schema state that no longer exists, the plan may show errors. In that case, review the diff carefully before proceeding.
- Seed data is not rolled back. Without
--seed, the pipeline does not touch it; with--seed, deploying an older commit seeds the rows committed there on top of the current data. - Static websites are not yet integrated into the generated pipeline. Static asset rollbacks must be performed manually.
Multi-environment example
A typical setup with staging and production:
bash
# Staging: main → stg (deploy on every push to main)
tailor setup ci branch --name my-app-stg
# Production: tagged commits → prod, with approval gate and branch guard
tailor setup ci tag --name my-app-prod \
--branch main --environment productionThen provision each workspace and set its id and the machine-user credentials on the matching environment (the staging target's environment defaults to my-app-stg; production uses production). tailor setup ci env prints the commands for both environments:
bash
tailor setup ci envCommit both workflow files and .github/tailor.lock.
GitHub Enterprise Server
The generated workflows also run on GitHub Enterprise Server. Prepare the server and its runners first:
- Runners: GitHub Enterprise Server has no GitHub-hosted runners. Change
runs-onon the managed jobs to the labels of your self-hosted runners; re-runningsetupkeeps it (see Customizing the generated workflow). The runners need Actions runner 2.327.1 or later, which runs the Node 24 actions the workflows use, andghandjqonPATH(plusnodewith--erd-preview). - Actions from github.com: the workflows use
tailor-platform/actions,actions/checkout,actions/setup-node,pnpm/action-setup, andoven-sh/setup-bun(andactions/upload-artifactwith--erd-preview). Make them available on the server with GitHub Connect oractions-sync. The server ships its own copies of theactions/*ones, which may not include the pinned versions: update them withactions-sync, or, with GitHub Connect, delete them from the server'sactionsorganization (see GitHub's Using the latest version of the official bundled actions). - Autofix workflow:
tailor setup ci autofixis not supported. - Environment settings: sign
ghin to the server (gh auth login --hostname <host>) before you runtailor setup ci envin a clone of the repository, so that theghcommands it prints and its Terraform output target the server. Whenghcannot tell the repository, the output says to exportGH_HOST=<host>before running theghcommands, orGITHUB_BASE_URL=https://<host>/before running Terraform, so that they call the server instead of github.com.
With --erd-preview, also pass --ghes:
bash
tailor setup ci branch --name my-app-stg --erd-preview --ghesGitHub Enterprise Server does not support actions/upload-artifact v4 or later, which the workflow uses to upload each ERD viewer unzipped. With --ghes, the workflow uploads it with v3 instead, so each viewer is a zip file, and the PR comment links to the workflow run, which lists the artifacts. setup update keeps --ghes; pass it again whenever you re-run setup ci branch, or the workflow switches back to the upload that GitHub Enterprise Server does not support.
Keeping dependencies and actions updated
Run this command once from the repository root to add Renovate configuration:
bash
tailor setup depsIt generates renovate.json, which extends Tailor's shared Renovate preset. The preset groups @tailor-platform/* package updates into one pull request and lets Renovate update the SHA-pinned GitHub Actions used by generated workflows. Enable Renovate for the repository by following the Renovate onboarding guide, then commit the generated file.
If the repository already has Renovate configuration, the command adds github>tailor-inc/renovate-config to its extends array in place instead of writing a new file, leaving your other settings untouched. It checks Renovate's standard root, .github, .gitlab, and .renovaterc locations, including the deprecated package.json configuration. When that configuration already extends the preset, the command reports that Renovate is set up and changes nothing.
JSONC and JSON5 configurations are updated in place while preserving their comments and formatting. Configurations that Renovate itself cannot load — such as invalid syntax, or duplicate keys outside JSON5 — are left unchanged and reported for manual inspection.
renovate.json is yours to edit — it is not tracked in .github/tailor.lock. Add your own rules freely; re-running tailor setup deps does not overwrite them. To remove it, delete the file.
Renovate updates the SDK dependency and action pins, but it does not regenerate the workflow template. After an SDK update, tailor setup check reports a template-version warning until you run tailor setup update.
Checking for drift
tailor setup check audits the workflows recorded in .github/tailor.lock against your current config and repository, without writing anything. It reports when a workflow file is missing or its SDK-managed parts were edited by hand, a job or step of yours uses the reserved tailor- prefix, a newer template is available, tailor.config.ts is no longer under the recorded --dir, or the repository default branch no longer matches a branch target's trigger. It audits the autofix workflow the same way, along with each app directory it runs tailor generate in. It also reports a .github/tailor-sdk.lock left by an older setup, with the files it generated that .github/tailor.lock does not track, since the workflows among them still run, and prints the git rm command that removes them. It exits non-zero when it finds drift, so you can run it in CI. Each finding names a stable rule key for future suppression. Run tailor setup update to regenerate the workflows it reports.
Workflows generated by setup ci branch, setup ci tag, and setup ci preview self-audit: each contains a tailor-drift-check step that runs the check in CI. Preview workflows run it alongside each preview deploy, so pull requests that deploy no preview (drafts with --require ready, fork PRs, and unlabeled PRs with --require label) skip the check. A single run audits every workflow recorded in .github/tailor.lock.
Drift findings are advisory by default. Set the repository variable TAILOR_PLATFORM_FAIL_ON_DRIFT to true to make unsuppressed findings fail the job. Execution and configuration errors fail regardless of this variable.
check compares only the generated files, .github/tailor.lock (and a leftover .github/tailor-sdk.lock), and the config; it does not read the GitHub Environment secrets and variables, so it runs the same on your own machine and in CI. Run tailor setup ci env to list what each environment needs.
Updating the generated workflow
When you upgrade the SDK, run tailor setup update from the repository root to pick up template improvements:
bash
tailor setup updateIt regenerates every workflow recorded in .github/tailor.lock with the flags each one was generated with, so you do not have to re-type setup ci branch, setup ci tag, and the rest one by one. Your own jobs, steps, and settings are kept (see Customizing the generated workflow). A branch that was detected from the repository default branch and a package manager that was detected from the repository root are detected again, and the parts that follow your config — the migration drift check, seed validation, and ERD preview namespaces — are derived from the current tailor.config.ts.
A workflow that cannot be regenerated, for example because you edited a managed part, does not stop the others: update regenerates the rest, then lists the workflows it could not update and exits non-zero. Revert the edit, or run tailor setup update --force to reset the managed parts of every workflow.
To change a flag for one target, re-run its setup ci subcommand with the new flags. .github/tailor.lock records them, and later updates reuse them.