GitHub Change Json
ActionsTags
(2)A GitHub Action to modify values in JSON and JSONC files during workflows. Supports nested keys, typed values, deep merge, array indices, schema validation, and more.
Sometimes you need to update a .json file during a CI/CD workflow:
- Publish the same package to GitHub Packages (
@myorg/pkg) and npm (pkg) with different names - Bump a version number during a release
- Update a
tsconfig.jsoncompiler option before deployment - Set the
homepagefield for GitHub Pages
This action handles all of these by modifying your JSON file in-place, preserving formatting and comments.
- uses: maxgfr/github-change-json@main
with:
key: 'version'
value: '2.0.0'
path: package.jsonname: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set scoped name for GitHub Packages
uses: maxgfr/github-change-json@main
with:
key: 'name'
value: '@my-org/my-package'
path: package.json
- name: Bump version and add build metadata
uses: maxgfr/github-change-json@main
with:
path: package.json
changes: |
[
{"key": "version", "value": "2.0.0"},
{"key": "private", "value": "false", "type": "boolean"},
{"key": "scripts", "value": "{\"prepublish\": \"tsc\"}", "merge": true}
]
schema: schemas/package.schema.json
commit: true- uses: maxgfr/github-change-json@main
with:
key: 'compilerOptions.target'
value: 'ES2020'
path: tsconfig.jsonWorks with JSONC files (e.g. tsconfig.json with comments) -- comments are preserved.
By default values are strings. Use type for numbers, booleans, or JSON objects:
- uses: maxgfr/github-change-json@main
with:
key: 'port'
value: '3000'
type: 'number' # stored as 3000, not "3000"
path: config.json
- uses: maxgfr/github-change-json@main
with:
key: 'compilerOptions.strict'
value: 'true'
type: 'boolean' # stored as true, not "true"
path: tsconfig.json
- uses: maxgfr/github-change-json@main
with:
key: 'scripts'
value: '{"build": "tsc", "test": "jest"}'
type: 'json' # stored as an object, not a string
path: package.jsonNumeric path segments are treated as array indices:
- uses: maxgfr/github-change-json@main
with:
key: 'contributors.0.name' # first element of the array
value: 'Alicia'
path: package.jsonMerge new keys into an existing object without overwriting untouched keys:
- uses: maxgfr/github-change-json@main
with:
key: 'scripts'
value: '{"start": "node .", "deploy": "fly deploy"}'
merge: true
path: package.json
# {"build":"tsc","test":"jest"} + merge → {"build":"tsc","test":"jest","start":"node .","deploy":"fly deploy"}Nested objects are recursively merged; arrays and primitives are replaced.
- uses: maxgfr/github-change-json@main
with:
key: 'devDependencies'
path: package.json
delete: true- uses: maxgfr/github-change-json@main
with:
path: package.json
changes: |
[
{"key": "name", "value": "@my-org/my-package"},
{"key": "version", "value": "2.0.0"},
{"key": "private", "value": "true", "type": "boolean"},
{"key": "scripts", "value": "{\"deploy\": \"fly deploy\"}", "merge": true},
{"key": "devDependencies", "delete": true}
]Validate the result against a JSON Schema before writing. If validation fails, the file is not modified:
- uses: maxgfr/github-change-json@main
with:
key: 'version'
value: '2.0.0'
path: package.json
schema: schemas/package.schema.json # local file path
# or: schema: 'https://json.schemastore.org/package.json'Create the target file with {} if it doesn't exist yet (parent directories are created automatically):
- uses: maxgfr/github-change-json@main
with:
key: 'database.host'
value: 'localhost'
path: config/settings.json
create-if-missing: truePreview what would change without modifying the file:
- uses: maxgfr/github-change-json@main
with:
key: 'name'
value: '@my-org/my-package'
path: package.json
dry-run: true- uses: maxgfr/github-change-json@main
with:
key: 'name'
value: '@my-org/my-package'
path: package.json
commit: trueAdd a Signed-off-by trailer to the commit message for DCO compliance:
- uses: maxgfr/github-change-json@main
with:
key: 'version'
value: '2.0.0'
path: package.json
commit: true
signoff: true
# Commit message will include:
# Signed-off-by: <GITHUB_ACTOR> <<GITHUB_ACTOR>@users.noreply.github.com>- id: update
uses: maxgfr/github-change-json@main
with:
key: 'version'
value: '2.0.0'
path: package.json
- run: |
echo "Old: ${{ steps.update.outputs.old-value }}"
echo "New: ${{ steps.update.outputs.new-value }}"
- if: steps.update.outputs.modified == 'true'
run: echo "File changed, deploying..."| Name | Type | Required | Default | Description |
|---|---|---|---|---|
path |
string | yes | -- | Path to the JSON file (relative to repo root) |
key |
string | no* | -- | Key to modify. Supports dot notation for nesting (a.b.c) and array indices (items.0.name). Escape literal dots with \\ (my\\.key) |
value |
string | no* | -- | Value to set (always passed as a string, converted via type) |
type |
string | no | string |
Value type: string, number, boolean, or json |
commit |
boolean | no | false |
Commit and push changes |
signoff |
boolean | no | false |
Add Signed-off-by trailer to the commit message (DCO) |
delete |
boolean | no | false |
Delete the key instead of setting a value |
merge |
boolean | no | false |
Deep merge a JSON object into the existing value |
dry-run |
boolean | no | false |
Preview changes without writing to disk |
create-if-missing |
boolean | no | false |
Create the file with {} if it doesn't exist |
changes |
string | no | -- | JSON array of changes (overrides single-key inputs). Each item: {"key", "value", "type", "delete", "merge"} |
schema |
string | no | -- | Path or URL to a JSON Schema to validate the result against |
*Either key or changes is required. value is required unless delete: true.
| Name | Description |
|---|---|
old-value |
Previous value (string for single key, JSON object for multiple keys) |
new-value |
New value after modification (same format as old-value) |
modified |
'true' if the file content changed, 'false' otherwise |
Files with line comments (//), block comments (/* */), and trailing commas are fully supported. Comments are preserved when modifying values.
The action detects and preserves the original file's:
- Indentation (2 spaces, 4 spaces, tabs)
- Line endings (LF, CRLF)
- Trailing newline
- Runs before writing -- the file is never left in an invalid state
- Works in
dry-runmode too (validates the would-be result) - Supports local file paths and
http:///https://URLs (30s fetch timeout) - Uses JSON Schema draft-07 via Ajv
$refto external URLs within the schema is not supported
When commit: true:
- Git user name is set to
GITHUB_ACTOR(fallback:github-actions[bot]) - Git user email is set to
<GITHUB_ACTOR>@users.noreply.github.com(fallback:github-actions@users.noreply.github.com) - Commit message format:
- Single key:
chore: update <path> (set <key>=<value>)/(delete <key>)/(merge <key>=<value>) - Multiple changes:
chore: update <path> with N changes - Long values are truncated to 50 characters in the commit message
- Single key:
- Pushed to
GITHUB_HEAD_REF(PR source branch) orGITHUB_REF(current ref) as fallback - Pre-commit hooks are bypassed (
--no-verify) - When
signoff: true, addsSigned-off-by: Name <email>trailer via--signoff - Skipped in
dry-runmode
The action fails with a clear message when:
- File not found (and
create-if-missingisfalse) - Invalid JSON/JSONC syntax in the target file
- Invalid type conversion (
type: numberwithvalue: abc,NaN,Infinity) - Invalid
typevalue (anything other thanstring,number,boolean,json) - Conflicting flags (
delete+mergeboth true) - Non-string
valueinchangesarray (e.g.{"value": 42}instead of{"value": "42"}) - Missing required fields (
keyorvaluewhen needed) - Invalid
changesinput (not valid JSON, not an array, missingkey) - Merge with non-JSON or non-object value
- Schema validation failure (with detailed per-field error messages)
- Schema file not found, invalid JSON, or invalid schema structure
- Schema URL fetch failure or timeout (30s)
- Setting a nested path through a primitive (
name.subwhennameis a string)
- String key modifications require an object root (
{}), not an array root ([]) - Purely numeric path segments are always array indices -- string keys like
"0"are not supported - Merge requires a JSON object value (not arrays or primitives)
- Schema
$refto external URLs is not resolved
pnpm install # install dependencies
pnpm run build # compile TypeScript
pnpm run package # bundle with ncc
pnpm run lint # run ESLint
pnpm run format # format with Prettier
pnpm test # run 165 tests
pnpm run all # build + package + lint + testMIT
Contributions are welcome! Please feel free to submit a Pull Request.
GitHub Change Json is not certified by GitHub. It is provided by a third-party and is governed by separate terms of service, privacy policy, and support documentation.