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 refused.
A share has one of three levels, and each includes the ones before it:
- watch: they only need live updates.
- read: they need to look things up or catch up.
- write: they work on the data with you. Write includes delete, so everyone with write access can change or remove anything under the prefix.
This recipe uses read. Share a List With Someone uses write, and Send Live Updates to Other Users uses watch.
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.
Share a List With Someone
explains it. Read is the default level of 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 });
Read access lets the coach load 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.
To see who has access, or to stop sharing, see Manage Who Has Access to Shared Data.
← All recipes