1 Overview
This NIP puts calendars on Nostr. A calendar event is an occurrence at a specific moment, or between two moments. Think of a meetup, a conference talk, a vacation, or a public holiday.
One word of caution on terms. Everywhere else in this manual, "event" means any signed Nostr event from NIP-01. This chapter always says calendar event for the scheduling object. It says event only for the underlying Nostr object.
The NIP defines four kinds. Kind 31922 is a date-based calendar event. Kind 31923 is a time-based calendar event. Kind 31924 is a calendar: a list that collects calendar events. Kind 31925 is an RSVP: a user's answer to a calendar event. All four are addressable events per NIP-01, and users can delete them per NIP-09.
A calendar event does not have to belong to a calendar. It stands on its own. Calendars only group and curate.
Addressable means: the relay keeps only the latest event per pubkey + kind + d value. Edit a calendar event by publishing a new event with the same d tag. Other events point at it with an a tag: <kind>:<pubkey>:<d value>.
2 Common Tags
Both calendar event kinds (31922 and 31923) share one set of tags. The client that creates the calendar event generates the d identifier.
| Tag | Use | Meaning |
|---|---|---|
| d | required | A short unique string identifier. Generated by the client that creates the calendar event. |
| title | required | The title of the calendar event. |
| summary | optional | A brief description of the calendar event. |
| image | optional | The URL of an image for the calendar event. |
| location | optional, repeated | A location: an address, GPS coordinates, a meeting room name, or a video-call link. |
| g | optional | A geohash. It ties the calendar event to a searchable physical location. |
| p | optional, repeated | A participant: 32-byte hex pubkey, optional recommended relay URL, and the participant's role in the meeting. |
| t | optional, repeated | A hashtag that categorizes the calendar event. |
| r | optional, repeated | A reference link: web page, document, video call, recorded video, and so on. |
| a | repeated | A reference to a kind 31924 calendar. It requests inclusion in that calendar. See below. |
The name tag is deprecated. It held the name of the calendar event. Read it only when title is absent. Never write it in new calendar events.
Collaborative calendar event requests
A calendar event can carry an a tag that points at a kind 31924 calendar. Clients should read this as a request: "add me to that calendar". The calendar owner approves by adding a matching a tag to the calendar. This lets many users contribute calendar events to calendars they do not own, subject to the owner's approval.
3 Date-Based Calendar Event — Kind 31922
A date-based calendar event starts on a date and ends before a later date. Use it when the time of day and the time zone do not matter: an anniversary, a public holiday, vacation days. It is an addressable event of kind 31922. The .content SHOULD be a description of the calendar event.
Besides the common tags, it takes two date tags:
| Tag | Use | Meaning |
|---|---|---|
| start | required | Inclusive start date in ISO 8601 format (YYYY-MM-DD). Must be less than end, if end exists. |
| end | optional | Exclusive end date in ISO 8601 format (YYYY-MM-DD). If omitted, the calendar event ends on the same date as start. |
{
"id": <32-byte lowercase hex sha256 of the serialized event>,
"pubkey": <32-byte lowercase hex public key of the creator>,
"created_at": <unix timestamp, seconds>,
"kind": 31922,
"content": "<description of calendar event>",
"tags": [
["d", "<random-identifier>"],
["title", "<title of calendar event>"],
// dates
["start", "<YYYY-MM-DD>"],
["end", "<YYYY-MM-DD>"],
// location
["location", "<location>"],
["g", "<geohash>"],
// participants
["p", "<hex pubkey>", "<relay URL>", "<role>"],
["p", "<hex pubkey>", "<relay URL>", "<role>"]
]
}
4 Time-Based Calendar Event — Kind 31923
A time-based calendar event spans a start time and an end time. It is an addressable event of kind 31923. The .content should be a description of the calendar event. It is required, but it can be an empty string.
Besides the common tags, it takes these time tags:
| Tag | Use | Meaning |
|---|---|---|
| start | required | Inclusive start as a Unix timestamp in seconds. Must be less than end, if end exists. |
| end | optional | Exclusive end as a Unix timestamp in seconds. If omitted, the calendar event ends instantaneously. |
| start_tzid | optional | Time zone of the start timestamp, from the IANA Time Zone Database. Example: America/Costa_Rica. |
| end_tzid | optional | Time zone of the end timestamp, from the IANA Time Zone Database. If omitted and start_tzid is present, the end uses the start's time zone. |
| D | required | The day-granularity Unix timestamp of a day the calendar event takes place on: floor(unix_seconds() / seconds_in_one_day). Include multiple D tags — one SHOULD exist for every day the calendar event covers. |
D tags. Relays index D, so clients can query one day cheaply.Two source quirks. First: the source's example tag ["D", "82549"] maps to a day in the year 2196; a present-day value is near 20600. Second: D is marked required, but it was added in 2026 — kind 31923 events published before then do not carry it. Query by #D with a fallback.
{
"id": <32-byte lowercase hex sha256 of the serialized event>,
"pubkey": <32-byte lowercase hex public key of the creator>,
"created_at": <unix timestamp, seconds>,
"kind": 31923,
"content": "<description of calendar event>",
"tags": [
["d", "<random-identifier>"],
["title", "<title of calendar event>"],
["summary", "<brief description>"],
["image", "<image URL>"],
// timestamps
["start", "<unix timestamp in seconds>"],
["end", "<unix timestamp in seconds>"],
["D", "<floor(unix seconds / 86400)>"],
["start_tzid", "<IANA time zone id>"],
["end_tzid", "<IANA time zone id>"],
// location
["location", "<location>"],
["g", "<geohash>"],
// participants
["p", "<hex pubkey>", "<relay URL>", "<role>"],
["p", "<hex pubkey>", "<relay URL>", "<role>"]
]
}
5 Calendar — Kind 31924
A calendar is a collection of calendar events. It is an addressable list event of kind 31924. One user can keep many calendars: personal, work, travel, meetups, conferences. The .content should be a detailed description of the calendar. It is required, but it can be an empty string.
| Tag | Use | Meaning |
|---|---|---|
| d | required | A universally unique identifier. Generated by the client that creates the calendar. |
| title | required | The calendar title. |
| a | repeated | A reference to a kind 31922 or 31923 calendar event that this calendar includes or responds to. |
A calendar can accept requests from other users. When a calendar event points at the calendar with an a tag, that is a request for inclusion. The owner answers by adding the calendar event's coordinates to the calendar.
{
"id": <32-byte lowercase hex sha256 of the serialized event>,
"pubkey": <32-byte lowercase hex public key of the creator>,
"created_at": <unix timestamp, seconds>,
"kind": 31924,
"content": "<description of calendar>",
"tags": [
["d", "<random-identifier>"],
["title", "<calendar title>"],
["a", "<31922 or 31923>:<author pubkey>:<d value>", "<relay URL>"],
["a", "<31922 or 31923>:<author pubkey>:<d value>", "<relay URL>"]
]
}
6 Calendar Event RSVP — Kind 31925
An RSVP answers a calendar event. It states the user's attendance intention. It is an addressable event of kind 31925. The .content is optional: a free-form note that adds context to the response.
Invitations work through p tags. If a calendar event tags a pubkey, read that as an invitation from the creator to that user. Clients MAY prompt the tagged user to RSVP. But any user may RSVP, tagged or not. Clients MAY prompt the calendar event creator to invite an untagged user who RSVP'd. Clients also MAY ignore such RSVPs.
Two things stay intentionally undefined. The NIP does not define who is authorized to attend when an untagged user RSVPs; the calendar event creator decides the semantics. The NIP also does not define what happens when a calendar event changes after an RSVP is submitted.
| Tag | Use | Meaning |
|---|---|---|
| a | required | Coordinates of the kind 31922 or 31923 calendar event being answered. |
| e | optional | The event id of the specific calendar event revision being answered. If present, clients SHOULD read the RSVP as an answer to that revision. Clients MAY treat it as not applying to other revisions. |
| d | required | A universally unique identifier. Generated by the client that creates the RSVP. |
| status | required | accepted, declined, or tentative. The attendance status for the referenced calendar event. |
| fb | optional | free or busy. States whether the user is free or busy for the duration of the calendar event. Omit or ignore this tag when status is declined. |
| p | optional | The pubkey of the calendar event's author. It lets clients query all RSVPs that pertain to that author. |
{
"id": <32-byte lowercase hex sha256 of the serialized event>,
"pubkey": <32-byte lowercase hex public key of the creator>,
"created_at": <unix timestamp, seconds>,
"kind": 31925,
"content": "<note>",
"tags": [
["e", "<kind 31922 or 31923 event id>", "<relay URL>"],
["a", "<31922 or 31923>:<author pubkey>:<d value>", "<relay URL>"],
["d", "<random-identifier>"],
["status", "<accepted / declined / tentative>"],
["fb", "<free / busy>"],
["p", "<hex pubkey of the calendar event author>", "<relay URL>"]
]
}
Source quirks in this section. The RSVP example JSON in the source is malformed: the e tag line lacks a closing quote and a trailing comma. Also, the d tag is a "short unique string" in the common-tags list, but a "universally unique identifier" here and for calendars. Treat both as: any client-generated unique string.
p tag invites. A kind 31925 event answers with a status.7 Intentionally Unsupported: Recurrence
This NIP does not support recurring calendar events. Recurrence brings heavy complexity: time zone differences between invitees, daylight saving time, leap years, multiple calendar systems, one-off changes in schedule or metadata.
The NIP pushes that complexity up to clients. A client that wants recurrence publishes individual calendar events with duplicated metadata, one per occurrence.
8 Worked Example
Alice organizes a meetup that runs from 17:00 UTC on 2026-06-15 to 01:00 UTC on 2026-06-16. It crosses midnight, so it carries two D tags. She invites Bob with a p tag and asks a community calendar to include the event.
{
"kind": 31923,
"pubkey": "a1ce4f2b…",
"created_at": 1780000000,
"content": "Monthly Nostr meetup. Talks, pizza, key signing.",
"tags": [
["d", "meetup-2026-06"],
["title", "Nostr Meetup #14"],
["summary", "Talks and pizza"],
["start", "1781542800"],
["end", "1781571600"],
["D", "20619"], ["D", "20620"],
["start_tzid", "Europe/Berlin"],
["location", "c-base, Rungestr. 20, Berlin"],
["g", "u33db2m"],
["p", "b0b1d2e3…", "wss://relay.example.com", "speaker"],
["t", "meetup"],
["r", "https://meetup.example.com/14"],
["a", "31924:ca1e4da5…:berlin-community"]
],
"id": …, "sig": …
}
Bob's client sees the p tag and prompts him. He accepts:
{
"kind": 31925,
"pubkey": "b0b1d2e3…",
"content": "See you there!",
"tags": [
["a", "31923:a1ce4f2b…:meetup-2026-06", "wss://relay.example.com"],
["e", "9e0c55aa…", "wss://relay.example.com"],
["d", "rsvp-77f3"],
["status", "accepted"],
["fb", "busy"],
["p", "a1ce4f2b…"]
],
"created_at": …, "id": …, "sig": …
}
Carol owns the community calendar. She approves Alice's request by adding the coordinates. A client then queries the day and the RSVPs:
-- Carol approves: her calendar now lists Alice's event -- → ["EVENT", {"kind":31924, "tags":[["d","berlin-community"], ["title","Berlin Community Calendar"], ["a","31923:a1ce4f2b…:meetup-2026-06","wss://relay.example.com"]], …}] ← ["OK", "c4a7e1…", true, ""] -- what happens on June 15? query the day bucket -- → ["REQ", "day", {"kinds":[31923], "#D":["20619"]}] ← ["EVENT", "day", {"kind":31923, "tags":[["title","Nostr Meetup #14"], …]}] ← ["EOSE", "day"] -- who is coming? query RSVPs by coordinates -- → ["REQ", "rsvps", {"kinds":[31925], "#a":["31923:a1ce4f2b…:meetup-2026-06"]}] ← ["EVENT", "rsvps", {"kind":31925, "tags":[["status","accepted"],["fb","busy"], …]}] ← ["EOSE", "rsvps"]
9 Version History
Terry Yiu proposed NIP-52 in mid-2023. The spec has since traded UUIDs for plain identifiers, gained RSVP pointers, and grown day-bucket indexing. Notable changes, newest first:
- 20262026-02Added the
Dday-granularity timestamp tag ("timehashes") to kind31923, so clients can query calendar events by day. - 20252025-08Added collaborative calendar event requests: an
atag on a calendar event now asks a kind31924calendar for inclusion. - 20252025-05Simplified the spec; dropped the UUID requirement —
dis now any client-generated unique string. - 20242024-08RSVPs gained the optional
e(revision) andp(author) tags, revised to stay backwards compatible with the requiredatag. Renamed "parameterized replaceable" events to "addressable". - 20232023-12Deprecated
namein favor oftitle; made.contentrequired; decoupled the calendar identifier from the calendar name. - 2023-252023-2025Housekeeping: kinds written as integers, NIP-12 mention removed, label cruft and typos fixed.
- Origin2023-06Terry Yiu published "NIP-52: Calendar Events" with the four kinds 31922-31925.
Original Proposal - Credits
| Author | Terry Yiu |
| First commit | 2023-06-10 — “NIP-52: Calendar Events” |
| Status | draft - optional — clients implement it to offer scheduling |
| Source | 52.md on GitHub - full change history |
10 See Also
- NIP-01 — the base protocol: addressable events,
a/e/ptags, and tag-filter queries used here. - NIP-09 — event deletion; calendar events are deletable through it.
- NIP-51 — lists; a kind
31924calendar is a purpose-built addressable list. - NIP-53 — live streaming and spaces; live activities that pair well with scheduled calendar events.
- NIP-19 —
naddrencoding for sharing the coordinates of a calendar event.