Let Someone View Your Data
A fitness app keeps each user's workouts in their own Puter account. A user who
trains with a coach wants the coach to see the log, but not change it. With
puter.kv.share() and read access, the coach can read and list
the keys under a prefix, and any write the coach tries is rejected.
A share has one of three levels, and each includes the ones before it:
- watch: the holder gets an event when a key changes, and can list the keys, but can't read their values.
- read: the holder can also read the values at any time.
- write: the holder can also change and delete any key under the prefix.
This recipe uses read. Share a List With Someone uses write, and Send Live Updates to Other Users adds events to a read share.
Share the Log
The app keeps each workout under workouts:<date>. Ask the user for permission
to share the workouts: prefix with puter.perms.request(),
then share it with the coach:
await puter.kv.set('workouts:2026-10-24', { type: 'run', km: 8 });
await puter.perms.request('kvShare', { prefix: 'workouts:', access: 'read' });
const share = await puter.kv.share({
prefix: 'workouts:',
recipient: 'coach_kim',
access: 'read',
label: 'Workouts from Dan',
});
The permission prompt only appears the first time. Read is the default level for
both puter.perms.request() and
puter.kv.share(), so you can leave out access for a read share.
Read the Log as the Coach
The coach finds every share made with them in your app with
puter.kv.listShared(), and passes each share's handle to
read it. Keys are relative to the shared prefix, so workouts:2026-10-24 is
2026-10-24 to the coach:
const { items } = await puter.kv.listShared();
for (const { handle, label, access } of items) {
const workouts = await puter.kv.list(true, { handle });
// [{ key: '2026-10-24', value: { type: 'run', km: 8 } }]
showClient(label, workouts, { canEdit: access === 'write' });
}
To read one day, pass the handle to puter.kv.get():
const run = await puter.kv.get('2026-10-24', { handle });
The coach reads the log whenever they open your app, so they always see the current data.
Show Edit Controls Only With Write Access
Each share from puter.kv.listShared() has an access field.
Show edit buttons only when it's 'write'. A write through a read share is
rejected with kv_handle_access_denied, so your app can also explain it when
it happens:
try {
await puter.kv.set('2026-10-25', { type: 'rest' }, { handle });
} catch (error) {
if (error.code === 'kv_handle_access_denied') {
showMessage('You can view this log, but not change it.');
}
}
Let Them Edit Later
To change the coach's access, share the same prefix with them again. This changes the existing share and keeps its handle, so the coach's app and its live updates keep working.
Your app's permission to share has a level too. A read permission covers
read and watch shares, so ask again with access: 'write' before giving
write access. The user sees a new prompt for it:
await puter.perms.request('kvShare', { prefix: 'workouts:', access: 'write' });
const raised = await puter.kv.share('workouts:', 'coach_kim', 'write');
// raised.handle === share.handle
Sharing again with 'read' sets it back.
Share Only Part of the Data
A share covers only the keys under its prefix. A budget app can share one month
with an accountant by sharing budget:2026-10:, and the other months stay
hidden.
Entries saved with disableSharing: true stay hidden even inside a
shared prefix, like this private note:
await puter.kv.set('workouts:2026-10-24:note', 'Knee hurt a bit', { disableSharing: true });
To the coach, puter.kv.get() returns null for it and
puter.kv.list() leaves it out. Through a write share, changing
it is rejected with kv_handle_access_denied, and deleting it leaves it in
place.
To see who has access, or to stop sharing, see Manage Who Has Access to Shared Data.
← All recipes