puter.kv.share() Shares the keys under a prefix with another Puter user, or with up to 50 users in one call.
The other user becomes the share's holder, and you stay its owner. The keys stay in your store, and the holder reaches them through the share.
Each share has a handle. The holder finds it with puter.kv.listShared() and passes it as handle to the other puter.kv methods, in the app whose data it is or outside any app. You can pass it too, with keys relative to the prefix, the same way the holder does. Sharing with other users shows how a holder uses a share.
puter.kv.share(prefix, recipient)
puter.kv.share(prefix, recipient, access)
puter.kv.share(prefix, recipients)
puter.kv.share(prefix, recipients, access)
puter.kv.share(options)
prefix (String) (required)
The key prefix to share, such as 'list:42:'. The share covers every key that starts with it. The trailing : is optional, and the returned prefix always ends with one.
A share stays on the prefix it was made for. Build the prefix from an id that never changes, such as list:<uuid>:, rather than from a name.
The segments between the : separators can use only lowercase letters (a-z), digits, _, ., and -, as in list:42: or room_1.draft-a:. A prefix can't be empty or have an empty segment such as list::42:. Any other prefix, such as List:42: or café:, is rejected with invalid_kv_share_prefix.
The longest prefix depends on the app's uid and the access level. For an app uid of the usual form, app- and a uuid, the prefix without its trailing : can be at most 168 characters for 'watch', 163 for 'read', and 162 for 'write'. In the store that belongs to no app, it can be at most 199, 194, and 193. A longer prefix is rejected with invalid_kv_share_prefix, and the error message gives the limit.
recipient (String | Object) (required)
The user to share with, as a username, { username }, or { uid } with the user's uuid. A username that contains @ is rejected with invalid_recipient.
recipients (Array)
The users to share with, from 1 to 50, each in any form recipient takes. Every recipient gets a share of the same prefix at the same access level, with the same label and notify setting. A user named twice gets one share. An empty array or more than 50 recipients is rejected with bad_request.
access (String) (optional)
What the holder can do. Defaults to 'read'. Each level includes the ones before it.
'watch': receive events when keys under the prefix change, and list the keys without their values with puter.kv.list(). Events leave out the value, even for a subscription that passes includeValue.'read': also read values with puter.kv.get() and puter.kv.list().'write': also change and delete keys with every other puter.kv method except puter.kv.flush(), such as puter.kv.set(), puter.kv.incr(), and puter.kv.del(). A holder with write access can change or delete any key under the prefix.options (Object)
The options form takes these properties:
| Option | Type | Description |
|---|---|---|
prefix |
String |
Required. The key prefix, as above. |
recipient |
String | Object |
Who to share with, as above. |
recipients |
Array |
Several users to share with, as above. Pass one of recipient or recipients. With both, recipient is ignored. |
access |
String |
What the holder can do, as above. Defaults to 'read'. |
label |
String | null |
Plain text the holder is shown with the share, such as 'Groceries from Dan'. Whitespace at either end is trimmed, and the rest can be at most 128 characters and 512 bytes of UTF-8. Control characters, invisible formatting characters (such as zero-width spaces and text direction marks), line breaks, and more than 3 combining marks on one character are rejected with kv_share_label_invalid. An empty string means no label. |
notify |
Boolean |
Whether you and the holder can send each other notifications through the share's handle with puter.notifications.notify(), from the app whose data it is. Defaults to false. Turning it on also sends the holder a kv.share.received notification, as What the holder sees describes. Shares of the store that belongs to no app can't carry notifications. |
appUuid |
String |
The app whose data to share. Outside an app, such as in a Node.js script using the user's auth token, it can name any app that exists. An app always shares its own data, and passing a different app's id fails with events_kv_handle_outside_namespace. |
Without an app, puter.kv uses a store that belongs to no app. Shares of that store have an appUid of null.
A Promise that resolves to the share:
handle (String): The share's id. The holder passes it as handle.prefix (String): The shared prefix, ending with :.access (String): 'watch', 'read', or 'write'.appUid (String | null): The app whose data is shared, or null for the store that belongs to no app.holder (String): The holder's username.label (String | null): The label the holder is shown.notify (Boolean): Whether you and the holder can notify each other through the share.createdAt (Number): When the share was made, in Unix seconds.With recipients, the Promise resolves to an array with one item per recipient, in the order you passed them. Each item has recipient, the recipient as you passed it, and either the share's properties listed above or error:
error (Object): Why that recipient didn't get a share, as { message, code }. Its code is one of the errors about a recipient, which are invalid_recipient, subject_does_not_exist, bad_request when it's you, invalid_kv_share_prefix when a share with that user has the same prefix in different letter case, recipient_not_accepting_shares, or events_kv_handle_limit_reached when you already have 256 live shares with that user.Any other error, such as a bad prefix, rejects the whole call. Before it shares with anyone, the call checks that all the new shares fit within your limits and rate limits, and rejects the whole call if they don't.
The promise rejects with { message, code }:
| Code | Meaning |
|---|---|
invalid_recipient |
A username contains @, or recipient isn't a username, { username }, or { uid }. |
subject_does_not_exist |
No user has that username or uuid. |
bad_request |
You named yourself as the recipient, appUuid isn't the id of an app that exists, or recipients is empty or has more than 50 entries. |
invalid_kv_share_prefix |
prefix is empty, too long, has an empty segment, or has a character other than lowercase letters, digits, _, ., and - between its separators. Also returned when one of your shares with the same user has the same prefix in different letter case. |
kv_share_access_invalid |
access isn't 'watch', 'read', or 'write'. |
kv_share_label_invalid |
label isn't a string or null, is too long, or contains a character it can't have. |
kv_share_notify_invalid |
notify isn't a boolean. |
recipient_not_accepting_shares |
The recipient blocked you, or doesn't accept shares. On an existing share, you raised its access, set a new label, or turned notify on. |
account_is_not_verified |
Your email address isn't confirmed. |
events_kv_handle_requires_account |
A temporary account tried to share. |
events_kv_handle_limit_reached |
You already have as many live shares as your plan allows, in total, in this app, or with this user. With recipients, the new shares would go over your limit in total or in this app. |
events_kv_handle_not_delegated |
An app shared without the user's consent for that prefix and level. |
events_kv_handle_outside_namespace |
An app named another app's data. |
events_kv_handle_owner_only |
The call came from an access token with less access than the account. |
forbidden |
An app shared through an access token it issued. |
kv_handles_disabled |
read and write shares are turned off on this deployment. |
events_kv_handles_disabled |
watch shares are turned off on this deployment. |
events_disabled |
Events, which watch shares need, are turned off on this deployment. |
too_many_requests |
Over the rate limit, or another change to the same share was still running. Try again. |
Sharing the same prefix of the same app's data with the same user again changes the existing share instead of making a second one. The share keeps its handle and createdAt, and gets the access of the new call. Use this to raise or lower a holder's access. Since access defaults to 'read', a call without it sets the share to 'read', lowering a 'write' share.
The holder's subscriptions through the handle keep working and follow the new access. Lowering a share to 'watch' stops values on its events, and raising it to 'read' starts them for subscriptions that asked for values.
Leave out label to keep the current label, pass a new one to replace it, or pass null to clear it. Leave out notify to keep the current setting, or pass true or false to change it.
A call that changes nothing, because every recipient already has the share with the same access, label, and notify, counts only toward the limit on named recipients. So your app can share again each time it opens to get the handle back. See Rate Limits and Quotas for the numbers.
Concurrent calls that share the same prefix with the same user usually make one share, and every call resolves to it. In rare cases they make two. puter.kv.listSharedByMe() lists both, and you can stop the extra one with puter.kv.unshare().
The holder never learns who shared or what the prefix is. The keys they read and write are relative to the prefix. The label is the only thing about the share you choose to tell them.
When a share of an app's data allows notifications, the holder gets a kv.share.received notification inside that app. It's sent when you make a share with notify: true, and when you turn notify on for an existing share, and for no other change. The holder gets at most 5 of these an hour from the same owner. A share past that is still made, without a notification. The Puter desktop doesn't show the notification, so your app is where the holder finds out about the share. The live notification needs notifications to be turned on for events on the deployment, so have the holder's app also call puter.kv.listShared() when it opens.
account_is_not_verified. Sharing doesn't need a phone number, a card, or a paid plan. A temporary account can't share and gets events_kv_handle_requires_account.puter.perms.request('kvShare', …). The consent names a prefix and a level. It covers that prefix and every key under it, at that level and the levels below it. A request without a level asks for 'read', the same default access has.recipient_not_accepting_shares. A block added later doesn't remove an existing share. You can still lower its access, clear its label with label: null, turn notify off, or stop it. Raising its access, setting a new label, or turning notify on fails with recipient_not_accepting_shares.See Rate Limits and Quotas for how many shares an account can make per minute and hold at once, and Key-value store for how much all the holders of your shares can read and write together.
puter.kv.unshare() - Stop a shareputer.kv.listSharedByMe() - List the shares you madeputer.kv.listShared() - List the shares made with youconst prefix = `list:${crypto.randomUUID()}:`;
await puter.kv.set(`${prefix}meta`, { title: 'Groceries' });
const share = await puter.kv.share(prefix, 'alice', 'write');
puter.print(`Shared with ${share.holder} as ${share.access}`);
const share = await puter.kv.share({
prefix: 'trip:japan-2027:',
recipient: 'alice',
access: 'write',
label: 'Japan 2027',
});
// Same prefix and user, so this changes the same share.
await puter.kv.share('trip:japan-2027:', 'alice', 'read');
const share = await puter.kv.share({
prefix: `room:${crypto.randomUUID()}:`,
recipient: 'alice',
access: 'write',
notify: true,
});
puter.print(`Alice can be notified through ${share.handle}`);
const results = await puter.kv.share('board:planning:', ['alice', 'bob', 'carol'], 'read');
for (const result of results) {
if (result.error) puter.print(`Not shared with ${result.recipient}: ${result.error.code}<br>`);
else puter.print(`Shared with ${result.holder}<br>`);
}