Skip to content

Members, sharing and notifications

interface MaypopMember {
id: string;
username: string;
role: string;
avatarUrl: string | null;
connected: boolean;
}
interface MaypopMemberList {
members: MaypopMember[];
guestCount: number;
}
members(): Promise<MaypopMemberList>;

members() returns everyone with access to the app — people it is shared with directly and through groups — sorted by username. connected means they had the app open in the last few minutes; guestCount is how many anonymous visitors have it open. It is a snapshot, so call it again to refresh. id matches maypop.user.id and KV author values for the same person.

const { members, guestCount } = await maypop.members();
renderFaces(
members.filter((m) => m.connected),
guestCount,
);

For live presence while people are in the app, use a multiplayer room.

interface MaypopLink {
to(path?: string): string;
unfurl(url: string): Promise<LinkPreview>;
}
share(opts: { path: string; title?: string }): Promise<void>;
readonly launchPath: string | null;

maypop.link.to("/poll/42") returns a Maypop link that opens your app on that screen. maypop.share({ path, title }) opens Maypop’s share card for it. When someone opens the link, the app starts with maypop.launchPath set to /poll/42 (a hash router picks it up on its own).

copyButton.onclick = () =>
navigator.clipboard.writeText(maypop.link.to(`/poll/${pollId}`));
shareButton.onclick = () =>
maypop.share({ path: `/poll/${pollId}`, title: "Vote on dinner" });
  • Paths must start with /. Build links with link.to, never from location, which is a sandboxed frame.
  • The link doesn’t grant access: whoever opens it gets what the app’s sharing settings allow.
  • share fails with maypop/share-unavailable before the app has been published and shared; its message is written for the person, so you can show it.
  • Label in-app buttons by what they share (“Share this poll”). Maypop’s own Share button already shares the whole app.

link.unfurl(url) fetches a public web page’s title, description and image on Maypop’s side, for link previews the browser couldn’t fetch itself:

interface LinkPreview {
url: string;
title?: string;
description?: string;
image?: string;
siteName?: string;
favicon?: string;
}
notify(opts: {
to: "all" | string[];
title: string;
body?: string;
path?: string;
}): Promise<void>;

Notifications appear in the Maypop bell and, for people who turned them on, as push notifications on their devices. to: "all" reaches everyone with access except the sender; otherwise pass member ids. path opens the app on that screen when tapped.

await maypop.notify({
to: "all",
title: "Poll closes in an hour",
body: "Dinner on Friday: pizza or tacos?",
path: `/poll/${pollId}`,
});
Limit Value
title 1–120 characters
body up to 500 characters
path up to 512 characters
Recipients up to 256 ids per call
Per app 30 an hour, in bursts of up to 10
Per person 5 an hour from one app

Over the limit, the call rejects with an error whose message is rate_limited. People who muted the app are skipped silently. Sending needs use access or more.

apps: { open(id: string): Promise<void> };

maypop.apps.open(appId) asks Maypop to open another app. It fails with maypop/app-not-found when the person can’t open it, and with maypop/unsupported where there is no Maypop shell around the app.