Now that we've seen how the private tempstore works, let's look at the shared one. The first thing we need to do in order to interact with it is to use the factory to create a new shared store:
/** @var \Drupal\Core\TempStore\SharedTempStoreFactory $factory */
$factory = \Drupal::service('user.shared_tempstore');
$store = $factory->get('my_module.my_collection');
However, unlike the private tempstore, we can pass a user identifier (ID or session ID) as a second parameter to the get() method to retrieve the shared store of a particular owner. If we don't, it defaults to the current user (logged in or anonymous).
Then, the simplest way we can store/read an entry is like before:
$store->set('my_key', 'my_value');
$value = $store->get('my_key');
Now, if we quickly jump to the database, we can see that the value column is the same as before, but the collection reflects that this is the shared store and the key is no longer prefixed by the owner. This is because another user should be able to retrieve the entry if they like. And the original owner can still be determined by checking the metadata of the entry:
$metadata = $store->getMetadata('my_key');
Also, we can delete it exactly as with the private store:
$store->delete('my_key');
Okay. However, what else can we do with the shared store that we cannot do with the other one?
First, we have two extra ways we can set an entry. We can set it if it doesn't already exist:
$store->setIfNotExists('my_key', 'my_value');
Alternatively, we can set it if it doesn't exist or it belongs to the current user (that is, the user owns it):
$store->setIfOwner('my_key', 'my_value');
Both these methods will return a Boolean, indicating whether the operation was successful or not. And essentially, they are handy to check for collisions. For example, if you have like a big piece of configuration that multiple users can edit, you can create the entry that stores the work in progress only if it doesn't exist, or if it exists and the current user owns it (virtually overwriting their own previous work, which may be okay).
Then, you also have the getIfOwner() and deleteIfOwner() methods that you can use to ensure that you only use or delete the entry if it belongs to the current user.
All this fuss, and for what? Why not just use the private store? This is because, in many cases, a flow can only be worked by one person at the time. So, if somebody started working on it, you will need to know in order to prevent others from working on it, but even more than that, you can allow certain users to "kick out" the previous user from the flow if they "went home without finishing it". They can then continue or clear out all the changes. It all depends on your use case.
Also, as a final point, the shared tempstore also works with the same expiration system as the private one.