Location sharing
Location sharing sends where a user is, either once or live for a while. This page covers the share dialog, how a live location keeps updating, and how a location renders in the message list.
What users get
The user opens the attachment menu and picks Location. The Share Location dialog finds their position. They can share it as it is now, or turn on "Share live location for" and pick 15 minutes, 1 hour or 8 hours. Share sends the location as a message of its own. Attach adds it to the message they're writing instead; it shows above the input, and goes out with their text when they send.
In the message list, a location shows as a card that opens the position in a map. A static location reads "Current location". A live one reads "Live until" and the end time, and the user's own has a Stop sharing button. When the time is up or they stop, it reads "Location sharing ended". While their live share runs, their app keeps sending their position, so the card stays current for everyone in the channel. In the channel list, a channel whose latest message is a location shows "Location" as its preview.




How it works across the layers
- State. The composer's
locationComposerholds the location being shared. A shared location is stored with its message asmessage.shared_location, not inmessage.attachments.LiveLocationManagerkeeps the user's live shares updated. - UI.
AttachmentSelectoropensShareLocationDialog. The composer shows an attached location above the input, andAttachmentrendersmessage.shared_locationwithGeolocation. - Layout. Not involved.
Sharing a location
ShareLocationDialog watches the browser's position with navigator.geolocation while it's open. Both buttons put that position into messageComposer.locationComposer, with the live duration when the user picked one:
- Attach leaves the location in the composer and closes the dialog. The next
MessageComposer.send()puts it on the message. - Share sends it at once with
MessageComposer.sendLocation(), as a message of its own. When that fails, the dialog publishes anapi:location:share:failednotification and stays open.
A location can't be shared in a thread: a thread's attachment menu has no Location entry, and MessageComposer.sendLocation() does nothing in a thread's composer. Sharing a location explains the calls behind both buttons, and Settings sets the shortest duration a live share can have.
Keeping a live location current
LiveLocationManager sends new positions for every live location the user is sharing. The React SDK creates one with useLiveLocationSharingManager:
- On start, the manager loads the user's active live locations with
client.getUserLiveLocations(). - When the user starts a new live share, the
live_location_sharing.startedevent adds it. Stopping it or deleting its message removes it. - While at least one share is active, the manager calls your
watchLocationfunction. Each position it reports is sent to every active share withclient.updateLiveLocation(), at most once everyliveLocationManager.minUpdateThrottleMs(3 seconds by default). - Each share is dropped when its end time passes. Stop sharing on the card calls
Channel.stopLiveLocationSharing().
The other clients see the new position on the message's message.updated event, and the card re-renders.
Turning it on or off
Location sharing is on by default. Two sides decide:
- The client:
messageComposer.location.enabled,trueby default. - The server: the channel type's
shared_locationssetting.
To turn it off in every composer:
client.config.set({
messageComposer: { location: { enabled: false } },
});The attachment menu offers Location only in channels, not in threads. Configuration lists every setting both sides decide.
Live locations need the manager
Without a LiveLocationManager, live shares are created, but their position never changes. Call useLiveLocationSharingManager once, in a component that stays mounted while the user is signed in, and pass it how to watch the position:
import type { StreamChat, WatchLocation } from "stream-chat";
import { useLiveLocationSharingManager } from "stream-chat-react";
// Declared once: a new function on every render would create a new manager.
const watchLocation: WatchLocation = (handler) => {
const watchId = navigator.geolocation.watchPosition((position) => {
handler({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
});
return () => navigator.geolocation.clearWatch(watchId);
};
export const LiveLocationReporter = ({ client }: { client: StreamChat }) => {
useLiveLocationSharingManager({ client, watchLocation });
return null;
};watchLocation receives a handler to call with each position, and returns a function that stops watching. To report less often, report on a timer instead, for example with navigator.geolocation.getCurrentPosition() every 30 seconds. The hook also takes getDeviceId, which sets the manager's deviceId; by default it's built from the device type, the browser and the user id, such as web-desktop-chrome-user-1. The device id stored on a new share comes from messageComposer.location.getDeviceId instead, a random id by default.
Customizing
Show a map in the dialog
The dialog has no map by default. Pass a GeolocationMap to it, and change the live durations with shareDurations, in milliseconds. This one uses react-leaflet:
import "leaflet/dist/leaflet.css";
import { MapContainer, Marker, TileLayer } from "react-leaflet";
import type {
ShareGeolocationMapProps,
ShareLocationDialogProps,
} from "stream-chat-react";
import { ShareLocationDialog } from "stream-chat-react";
const DialogMap = ({
geolocationPositionError,
latitude,
loadingLocation,
longitude,
restartLocationWatching,
}: ShareGeolocationMapProps) => {
if (loadingLocation) return <div>Finding your location…</div>;
if (geolocationPositionError || latitude == null || longitude == null) {
return (
<div>
<p>Your location isn't available.</p>
<button onClick={restartLocationWatching} type="button">
Try again
</button>
</div>
);
}
return (
<MapContainer
center={[latitude, longitude]}
style={{ height: 300, width: "100%" }}
zoom={13}
>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[latitude, longitude]} />
</MapContainer>
);
};
const shareDurations = [5 * 60 * 1000, 30 * 60 * 1000, 2 * 60 * 60 * 1000];
export const ShareLocationDialogWithMap = (props: ShareLocationDialogProps) => (
<ShareLocationDialog
{...props}
GeolocationMap={DialogMap}
shareDurations={shareDurations}
/>
);Register it with <WithComponents overrides={{ ShareLocationDialog: ShareLocationDialogWithMap }}>. Durations shorter than one minute are left out. When the location isn't available, the example's Try again button calls restartLocationWatching. It stops the dialog's current position watch and starts a new one. A map in the Share Location dialog explains every prop of ShareGeolocationMapProps.

Show a map in the message
Geolocation shows a placeholder that links to a map. Give it a GeolocationMap, which receives latitude and longitude, and pass it to Attachment:
import { MapContainer, Marker, TileLayer } from "react-leaflet";
import type {
AttachmentProps,
GeolocationMapProps,
GeolocationProps,
} from "stream-chat-react";
import { Attachment, Geolocation } from "stream-chat-react";
const MessageMap = ({ latitude, longitude }: GeolocationMapProps) => (
<MapContainer
center={[latitude, longitude]}
style={{ height: 200, width: "100%" }}
zoom={13}
>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[latitude, longitude]} />
</MapContainer>
);
const GeolocationWithMap = (props: GeolocationProps) => (
<Geolocation {...props} GeolocationMap={MessageMap} />
);
export const AttachmentWithMaps = (props: AttachmentProps) => (
<Attachment {...props} Geolocation={GeolocationWithMap} />
);Register it as the Attachment override. A live card re-renders with each new position, so the map follows. Geolocation also takes GeolocationAttachmentMapPlaceholder, the component shown when there's no map.
The location preview above the input is the location item of the composer's preview set. To replace it, swap that item's Component in your own set, as Custom previews shows.
Reference
- Attachments: the
Attachmentprops, includingGeolocation. - Attachment Selector: the menu that opens the dialog.
- Location hooks:
useLiveLocationSharingManager. - MessageComposer API:
locationComposerandsendLocation(). - Configuration tree:
messageComposer.locationandliveLocationManager.