Skip to content

maxgfr/github-change-json

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

github-change-json

View Action Tests Integration

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.

Why

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.json compiler option before deployment
  • Set the homepage field for GitHub Pages

This action handles all of these by modifying your JSON file in-place, preserving formatting and comments.

Quick Start

- uses: maxgfr/github-change-json@main
  with:
    key: 'version'
    value: '2.0.0'
    path: package.json

Full Workflow Example

name: 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

Examples

Nested Keys (dot notation)

- uses: maxgfr/github-change-json@main
  with:
    key: 'compilerOptions.target'
    value: 'ES2020'
    path: tsconfig.json

Works with JSONC files (e.g. tsconfig.json with comments) -- comments are preserved.

Typed Values

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.json

Array Indices

Numeric 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.json

Deep Merge

Merge 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.

Delete a Key

- uses: maxgfr/github-change-json@main
  with:
    key: 'devDependencies'
    path: package.json
    delete: true

Multiple Changes at Once

- 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}
      ]

Schema Validation

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 File if Missing

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: true

Dry Run

Preview 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

Commit and Push

- uses: maxgfr/github-change-json@main
  with:
    key: 'name'
    value: '@my-org/my-package'
    path: package.json
    commit: true

Commit with Sign-off (DCO)

Add 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>

Use Outputs

- 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..."

Inputs

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.

Outputs

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

Behavior Details

JSONC Support

Files with line comments (//), block comments (/* */), and trailing commas are fully supported. Comments are preserved when modifying values.

Formatting Preservation

The action detects and preserves the original file's:

  • Indentation (2 spaces, 4 spaces, tabs)
  • Line endings (LF, CRLF)
  • Trailing newline

Schema Validation

  • Runs before writing -- the file is never left in an invalid state
  • Works in dry-run mode too (validates the would-be result)
  • Supports local file paths and http:// / https:// URLs (30s fetch timeout)
  • Uses JSON Schema draft-07 via Ajv
  • $ref to external URLs within the schema is not supported

Commit Behavior

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
  • Pushed to GITHUB_HEAD_REF (PR source branch) or GITHUB_REF (current ref) as fallback
  • Pre-commit hooks are bypassed (--no-verify)
  • When signoff: true, adds Signed-off-by: Name <email> trailer via --signoff
  • Skipped in dry-run mode

Error Handling

The action fails with a clear message when:

  • File not found (and create-if-missing is false)
  • Invalid JSON/JSONC syntax in the target file
  • Invalid type conversion (type: number with value: abc, NaN, Infinity)
  • Invalid type value (anything other than string, number, boolean, json)
  • Conflicting flags (delete + merge both true)
  • Non-string value in changes array (e.g. {"value": 42} instead of {"value": "42"})
  • Missing required fields (key or value when needed)
  • Invalid changes input (not valid JSON, not an array, missing key)
  • 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.sub when name is a string)

Limitations

  • 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 $ref to external URLs is not resolved

Development

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 + test

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

About

Github action which lets you to change a value from a json file (e.g. package.json)

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages