Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→edit-package-jsonnext

edit-package-json0.10.6

Edit package.json without parsing, as string, to keep the formatting intact

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • PURPOSE
  • API — SET()
  • WRITING NEW…
  • API — DEL()
  • API — VERSION
  • API — TYPES
  • Changelog

Permalink to InstallationInstallation

Permalink to Quick TakeQuick Take

Permalink to ExamplesExamples

  • Add a key that is not there yet, at the indentation the file already uses
  • Delete an array item by its dot-path index
  • Delete a nested object key
  • Minimal example, del()
  • Minimal example, set()
  • Replace an existing array item by its dot-path index
  • Replace an existing nested value with null
  • Set a numeric value without changing surrounding formatting
  • Replace an object value while preserving outer formatting

Purpose

Edit JSON contents as strings to guarantee the formatting will be intact.

API uses object-path notation to set values on any path in JSON — one that’s already there or one that isn’t yet.

It’s powering the update-versions CLI.

API — set()

The function set() is imported like this:

It’s a function which takes three input arguments:

Input argumentTypeObligatoryDescription
str
Type: String
Obligatory: yes
strStringyesJSON file contents
path
Type: String
Obligatory: yes
pathStringyesPath in the object, must follow object-path notation
valToInsert
Type: Whatever
Obligatory: yes
valToInsertWhateveryesWhat to insert at the given path

Function returns an amended string.

If the path is already there, its value is replaced. If it’s not, it’s written in — see writing new paths below.

import { set } from "edit-package-json";

// the path is there, so the value is replaced
set('{\n  "version": "1.0.0"\n}', "version", "2.0.0");
// => '{\n  "version": "2.0.0"\n}'

// the path is not there, so it's written in
set('{\n  "version": "1.0.0"\n}', "license", "MIT");
// => '{\n  "version": "1.0.0",\n  "license": "MIT"\n}'

Setting a key which is already there replaces it, it’s never duplicated.

The only thing set() throws on is a missing or empty first input argument.

Writing new paths

Earlier versions could only amend paths which already existed — asking for anything else got you your string back, untouched. Now set() writes those paths in.

// two-space indentation stays two-space
set('{\n  "a": "b"\n}', "c", "d");
// => '{\n  "a": "b",\n  "c": "d"\n}'

// tabs stay tabs
set('{\n\t"a": "b"\n}', "c", "d");
// => '{\n\t"a": "b",\n\t"c": "d"\n}'

// minified stays minified
set('{"a":"b"}', "c", "d");
// => '{"a":"b","c":"d"}'

// the gap after the colon is copied as well
set('{"a": "b"}', "c", "d");
// => '{"a": "b","c": "d"}'

// the gap in front of a member is copied too
set('{ "a": "b" }', "c", "d");
// => '{ "a": "b", "c": "d" }'

Missing containers are created

You don’t have to write a path one level at a time. Whichever part of the chain is missing gets built for you, indented to match:

set('{\n  "name": "demo"\n}', "dependencies.uvu", "^0.5.0");
// => '{\n  "name": "demo",\n  "dependencies": {\n    "uvu": "^0.5.0"\n  }\n}'

// several levels at once
set('{"a":"b"}', "c.d.e", "f");
// => '{"a":"b","c":{"d":{"e":"f"}}}'

// an empty container gets opened up
set('{\n  "scripts": {}\n}', "scripts.test", "uvu");
// => '{\n  "scripts": {\n    "test": "uvu"\n  }\n}'

Whether a created container is an object or an array is decided the way object-path decides it — an all-digits path segment asks for an array, anything else asks for an object:

set('{"a":"b"}', "files.0", "dist");
// => '{"a":"b","files":["dist"]}'

// "01" is not an array index, it's an object key
set('{"a":"b"}', "c.01", "d");
// => '{"a":"b","c":{"01":"d"}}'

Arrays

An index within an array’s existing length is amended in place, an index at the end appends, and an index past the end pads with null — again, matching object-path:

set('{"a":["p","q"]}', "a.0", "z");
// => '{"a":["z","q"]}'

set('{"a":["p"]}', "a.1", "q");
// => '{"a":["p","q"]}'

set('{"a":[]}', "a.2", "x");
// => '{"a":[null,null,"x"]}'

Values which are not strings

Numbers, booleans, null, objects and arrays are all written in as JSON:

set('{"a":"b"}', "c", 42);
// => '{"a":"b","c":42}'

set('{"a":"b"}', "c", true);
// => '{"a":"b","c":true}'

set('{"a":"b"}', "c", null);
// => '{"a":"b","c":null}'

set('{"a":"b"}', "publishConfig", { access: "public" });
// => '{"a":"b","publishConfig":{"access":"public"}}'

set('{"a":"b"}', "files", ["dist"]);
// => '{"a":"b","files":["dist"]}'

Mind you, a string which merely looks like JSON is written in as a string, not as a container. Pass the real object or array if that’s what you’re after.

When there’s nowhere to write

object-path throws on the cases below. Since this package never parses your string, there’s nothing it could throw about with any confidence, so your input comes back exactly as it went in:

// there's nothing to add a key to - "a" is a string
set('{"a":"b"}', "a.c", "x");
// => '{"a":"b"}'

// an array takes indexes, not keys
set('{"a":[]}', "a.x", "y");
// => '{"a":[]}'

// an empty path segment
set('{"a":"b"}', "c..d", "x");
// => '{"a":"b"}'

API — del()

The function del() is imported like this:

It’s a function which takes two input arguments:

Input argumentTypeObligatoryDescription
str
Type: String
Obligatory: yes
strStringyesJSON file contents
path
Type: String
Obligatory: yes
pathStringyesDesired path in the object to delete, must follow object-path notation

Function returns an amended string.

API — version

You can import version:

API — types

This package is written in TypeScript and exports the type Inputs — the internal request shape set() and del() build — the source str, the path, the mode ("set", "del" or "locate", the last one being how set() finds the deepest container a new path can hang off) and, for set(), the valToInsert.

import type { Inputs } from "edit-package-json";

Permalink to changelogChangelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab