Reusable GitHub Actions workflows for FiveM projects. Add a small caller workflow to your repository and GitHub will run the implementation from this repository.
The examples below use the v1 release of these workflows. Pin to a commit SHA instead if your repository requires immutable action references.
| Workflow | Use | Required secret |
|---|---|---|
discord-push.yml |
Send a Discord notification for commits on the default branch | DISCORD_WEBHOOK_URL |
discord-release.yml |
Send a Discord changelog when a GitHub release is published | DISCORD_WEBHOOK_URL |
ci.yml |
Install dependencies and run pnpm lint, test, and build scripts | None |
lint.yml |
Lint a FiveM Lua resource and publish a JUnit check report | None by default; optional App credentials |
release.yml |
Prepare a version tag, or build and publish a tagged release | None by default; optional App credentials |
All workflows are called at the job level with uses:. They are not copied into the consuming repository.
Standalone caller examples are available in examples/.
Create .github/workflows/ci.yml to run the standard pnpm checks on pushes and pull requests:
name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
ci:
uses: Red40-Development/red40-public-workflows/.github/workflows/ci.yml@v1The reusable workflow requires package.json and pnpm-lock.yaml, installs with pnpm install --frozen-lockfile, and runs lint, test, and build when enabled. Missing scripts are skipped. For a project in a subdirectory, set working_directory; node_version, pnpm_version, and the three run_* inputs are also available.
Create .github/workflows/discord-push.yml in your repository:
name: Discord commit notification
on:
push:
branches: [main]
jobs:
notify:
uses: Red40-Development/red40-public-workflows/.github/workflows/discord-push.yml@v1
with:
# Optional. Omit this to use the default Red40 avatar.
avatar_url: https://example.com/my-avatar.png
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}The reusable workflow also checks that the push is to the repository's configured default branch. Replace main in the caller if your default branch has another name.
Create DISCORD_WEBHOOK_URL under Repository settings → Secrets and variables → Actions. Treat the webhook URL as a password.
Create .github/workflows/discord-release.yml:
name: Discord release notification
on:
release:
types: [published]
jobs:
notify:
uses: Red40-Development/red40-public-workflows/.github/workflows/discord-release.yml@v1
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}Optional inputs are avatar_url, footer_icon_url, footer_title, remove_github_reference_links, and color:
with:
avatar_url: https://example.com/my-avatar.png
footer_icon_url: https://example.com/my-footer-icon.png
footer_title: "My Development Bot"
remove_github_reference_links: false
color: "16736808"
Create .github/workflows/lint.yml:
name: Lint
on:
push:
branches: [main]
workflow_dispatch:
jobs:
lint:
uses: Red40-Development/red40-public-workflows/.github/workflows/lint.yml@v1
permissions:
contents: read
checks: write
with:
# Optional. The default is suitable for the standard Red40/Qbox setup.
extra_libs: ox_lib+mysql+qblocales+qbox+qbox_playerdata+qbox_libextra_libs is a +-separated list of additional FiveM resources to make available to the linter. The reusable job is restricted to the default branch, so calls from other refs are skipped.
The release workflow has two operations:
preparevalidates the version, updatesfxmanifest.lua, commits the change, and creates an annotated tag.publishchecks out that tag, optionally buildsweb/, creates a ZIP archive, generates changelog notes, and creates or updates the GitHub release.
Create .github/workflows/release.yml:
name: Release
on:
workflow_dispatch:
inputs:
operation:
description: Release operation
required: true
type: choice
options:
- prepare
- publish
version:
description: Release tag, for example v1.2.3
required: true
type: string
jobs:
release:
uses: Red40-Development/red40-public-workflows/.github/workflows/release.yml@v1
permissions:
contents: write
with:
operation: ${{ inputs.operation }}
version: ${{ inputs.version }}Run prepare first. After it succeeds, run publish with the same version. Versions must start with v, for example v1.2.3, and the repository must contain an fxmanifest.lua with a version directive.
The example above is intentionally manual and works with only GITHUB_TOKEN. If you want prepare to automatically trigger publish when it pushes the version tag, use examples/release-on-tag.yml:
name: Create Release
on:
workflow_dispatch:
inputs:
version:
description: Release tag (for example, v1.2.3)
required: true
type: string
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
name: ${{ github.event_name == 'workflow_dispatch' && 'Prepare release' || 'Publish release' }}
uses: Red40-Development/red40-public-workflows/.github/workflows/release.yml@v1
with:
operation: ${{ github.event_name == 'workflow_dispatch' && 'prepare' || 'publish' }}
version: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
secrets:
CLIENT_APP_ID: ${{ secrets.CLIENT_APP_ID }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}This automatic pattern requires the optional GitHub App credentials described below. A tag pushed with GITHUB_TOKEN does not create another Actions run, so without the App the publish phase must be started manually.
By default, the bundle includes files that are not ignored by Git, excludes .github, .git, .vscode, .assets, node_modules, and ZIP files, and includes web/build/ when present. Add a .cfxrelease file to explicitly select files with one glob pattern per line:
fxmanifest.lua
client/**/*.lua
server/**/*.lua
shared/**/*.lua
web/build/**
Lint and release use the caller repository's built-in GITHUB_TOKEN by default. The caller must grant it the permissions shown in the examples: checks: write for lint and contents: write for releases.
You only need a GitHub App when the workflow must access repositories outside the caller repository—for example, private submodules—or when you want a dedicated bot identity for release commits. If you provide both optional secrets, the reusable workflow uses the App token instead of GITHUB_TOKEN.
Create and install the App as follows:
- Open Settings → Developer settings → GitHub Apps → New GitHub App. For an organization-owned app, use the organization’s developer settings.
- Give the app a name and homepage URL. Webhooks and user authorization are not needed by these workflows, so leave them disabled unless you need them for another purpose.
- Under Repository permissions, select:
- Checks: Read and write for
lint.yml. - Contents: Read and write for
release.yml. - If using both workflows, select both permissions.
- Checks: Read and write for
- Create the app. On the app’s settings page, copy the Client ID. This is the value used as
CLIENT_APP_ID; it is different from the numeric App ID. - In the app settings, select Generate a private key. GitHub downloads a
.pemfile. Store its complete contents, including theBEGINandENDlines, as thePRIVATE_KEYActions secret. Never commit this file. - Select Install App, choose your user or organization, and grant access to the repositories that will use these workflows. Choose Only select repositories when possible.
- Add the values to each consuming repository under Settings → Secrets and variables → Actions → Secrets:
CLIENT_APP_ID: the Client ID copied in step 4.PRIVATE_KEY: the complete PEM private key from step 5.
Pass those optional secrets to the reusable workflow when needed:
secrets:
CLIENT_APP_ID: ${{ secrets.CLIENT_APP_ID }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}The app must be installed on the repository before a lint or release run. If the release workflow checks out private submodules, install the app on those repositories too. Changes to App permissions may require approving the updated installation again.
Keep the private key secret and rotate it by generating a new key and replacing PRIVATE_KEY if it is exposed. GitHub App installation tokens generated during a run expire after one hour.
Resource not accessible by integration: confirm the caller grants the requiredGITHUB_TOKENpermission. If using an App, also confirm its permission, installation scope, and secret names.- Lint check creation fails: grant the caller
checks: write, or grant the optional App Checks: Read and write permission. - Release cannot push a tag or create a release: grant the caller
contents: write, or grant the optional App Contents: Read and write permission. - The job is skipped:
discord-push.ymlandlint.ymlintentionally run only when the triggering ref is the repository’s default branch.