Add Counters


Many apps count something: how often the user opened the app, how many words they wrote, how many hints they have left. puter.kv is your database in Puter.js, and it has counter methods built in. The database does the addition for you, so a count stays accurate even when two tabs update it at the same moment. Store data covers the basic reads and writes.

Count Up

To add one, use the puter.kv.incr() method. It returns the new value:

const opens = await puter.kv.incr('opens');

A key that doesn't exist yet starts at 0, so the first call returns 1. There is nothing to set up first.

To add more than one, pass the amount:

await puter.kv.incr('wordsWritten', 250);

The addition happens inside the database in a single write, so two calls at the same moment always add two. The key must hold a number. When it holds anything else, such as the text '5', the call rejects with value_not_a_number and the stored value stays as it was.

Count Down

To subtract, use the puter.kv.decr() method. It works the same way and also returns the new value:

await puter.kv.decr('unread');

A counter keeps going below zero, so a counter at 0 goes to -1. When a count must stay at zero or above, check the value it returns and undo the step if it went too far:

const hintsLeft = await puter.kv.decr('hintsLeft');

if ( hintsLeft < 0 ) {
    await puter.kv.incr('hintsLeft');   // there was nothing left to use
} else {
    showHint();
}

Checking the returned value is safe when two tabs spend the last hint at once. Only one of them sees 0, and the other sees -1 and gives it back.

Read a Counter

To show the count, read it with the puter.kv.get() method. A key that was never counted, or whose TTL ran out, comes back as null, so default it to 0:

const opens = await puter.kv.get('opens') ?? 0;

Count Several Things in One Key

So far each count has its own key, such as opens or hintsLeft. When an app tracks several counts that belong together, it can keep them all in one key instead. The value of that key is an object, and each count is a field of the object.

For example, a writing app can keep all of its usage counts in one object.

{
    opens: 12,
    wordsWritten: 4500,
    hintsLeft: 3,
    features: { export: 2 },
}

To add to these counts, pass an object with the fields you want to change to puter.kv.incr(). The value of each field is the amount to add. All the fields change in the same write, and the call returns the whole stored object:

const stats = await puter.kv.incr('stats', { opens: 1, wordsWritten: 250 });
// { opens: 13, wordsWritten: 4750, hintsLeft: 3, features: { export: 2 } }

To reach a field inside a nested object, such as features, write its path with dot notation:

await puter.kv.incr('stats', { 'features.export': 1 });
// { opens: 13, wordsWritten: 4750, hintsLeft: 3, features: { export: 3 } }

Fields left out of the call, such as hintsLeft, stay as they are. A field that doesn't exist yet starts at 0, and missing objects along the path are created for you.

Count per Day

To count per day, put the date in the key. Each day gets a fresh key that starts at 0:

const today = new Date().toISOString().slice(0, 10);   // '2026-09-30', in UTC

await puter.kv.incr(`opens:${ today }`);

To have old days delete themselves, set a TTL with the puter.kv.expire() method the first time a day is counted. Only one call ever gets 1 back, so the TTL is set once. Later calls to puter.kv.incr() keep that TTL as it is:

const key = `opens:${ today }`;
const count = await puter.kv.incr(key);

if ( count === 1 ) {
    await puter.kv.expire(key, 60 * 60 * 24 * 90);   // keep 90 days
}

Query a collection shows how to read a range of dated keys back, such as one month.

Reset a Counter

To start over, delete the key with the puter.kv.del() method. The next puter.kv.incr() starts from 0 again:

await puter.kv.del('opens');

Notes

← All recipes