Built-in Events
Reference of the events emitted by VitNode core and first-party plugins, with payloads and real use cases.
Reference of every event VitNode and its first-party plugins emit today. Listen
to any of them from your own plugin with
buildEventListener - no imports from
the emitting plugin are needed, the event map is global.
| Event | Payload | Emitted when |
|---|---|---|
user.created | { userId, email, name, emailVerified } | A user is created - sign-up, AdminCP, or SSO first sign-in |
user.updated | { userId, email, name } | A user is edited in the AdminCP (profile or roles) |
user.deleted | { userId, email } | Declared only - core has no user deletion flow yet |
role.created | { roleId } | A role is created in the AdminCP |
role.updated | { roleId } | A role is edited in the AdminCP |
role.deleted | { roleId } | A role is deleted in the AdminCP |
blog.post.created | { postId, categoryId } | A blog post is created |
blog.post.updated | { postId, categoryId } | A blog post is edited |
blog.post.deleted | { postId, categoryId } | A blog post is deleted |
blog.category.created | { categoryId } | A blog category is created |
blog.category.updated | { categoryId } | A blog category is edited |
blog.category.deleted | { categoryId, postIds } | A blog category (and its posts, via cascade) is deleted |
Core
user.created
Emitted after a user row is committed to core_users, from every creation
path: the public sign-up form, user creation in the AdminCP, and the first
sign-in through an SSO provider.
Prop
Type
Use cases: send a welcome or verification email (dispatch a
queue task so it retries), subscribe the user to a
newsletter audience, provision plugin-owned data (a profile row, default
settings), or notify moderators about new registrations via
c.get("realtime").
export const welcomeListener = buildEventListener({
event: "user.created",
name: "send-welcome-email",
handler: async (c, payload) => {
await c.get("queue").dispatch({
name: "send-welcome-email",
payload: { userId: payload.userId, email: payload.email },
});
},
});user.updated
Emitted after a user is edited in the AdminCP - profile fields (email, name, name code) and/or role assignments. The payload carries the user's current values after the update.
Prop
Type
Use cases: sync the user's identity into an external system (CRM, mailing
list), invalidate plugin-owned caches keyed by user, or audit-log staff edits
using the envelope's actor.
role.created / role.updated
Emitted after a role is created or edited in the AdminCP (including its
translated names, which live in core_languages_words).
Prop
Type
Use cases: provision plugin-side permission defaults for a new role, or refresh externally-cached permission matrices when a role changes.
role.deleted
Emitted after a role is deleted in the AdminCP. Its translated names in
core_languages_words are removed with it, and its secondary-role assignments
and staff-permission entries are dropped by database cascade. If the role still
had members, they are reassigned to another role before the delete, so by
the time this fires no user references the removed role.
Prop
Type
Use cases: clean up plugin-owned data keyed by role id (permission matrices,
per-role settings, externally-cached role lists), or audit-log the removal using
the envelope's actor.
The event fires only after the delete transaction commits, so by the time your
listener runs the role row is gone - key your cleanup off payload.roleId
rather than re-reading core_roles:
export const roleCleanupListener = buildEventListener({
event: "role.deleted",
name: "cleanup-role-settings",
handler: async (c, payload) => {
// Remove any plugin-owned rows keyed by the deleted role.
await c
.get("db")
.delete(blog_role_settings)
.where(eq(blog_role_settings.roleId, payload.roleId));
},
});user.deleted (declared only)
This event exists in the VitNodeEvents map so listeners and payloads are
already typed, but core never emits it today - there is no user deletion
flow yet. It is the agreed-upon name for plugins that implement account deletion
themselves, and core will emit it once deletion lands.
Blog (@vitnode/blog)
blog.category.created / blog.category.updated
Emitted after a category (and its translated titles) is created or edited in the AdminCP.
Prop
Type
Use cases: keep navigation menus or externally-cached category trees in sync, or notify an external CMS/feed of taxonomy changes.
blog.post.created / blog.post.updated / blog.post.deleted
Emitted from the AdminCP post routes after the post (and its translations and search index entries) are written.
Prop
Type
Use cases: push a realtime "new post" notification to subscribers, ping a webhook (via a queue task) that shares the post to social media, invalidate an external cache/CDN, or keep plugin-owned derived data (reading lists, related posts) in sync.
blog.category.deleted
Emitted after a category is deleted. Deleting a category cascade-deletes its posts at the database level, so the payload carries the ids of the posts that were removed with it.
Prop
Type
Use cases: the blog plugin itself ships a listener on this event
(cleanup-category-search) that removes the cascade-deleted posts from the
search index - a good template for cleaning up any data your plugin keys by
post id.
Deliberately not emitted (yet)
High-frequency or consumer-less events are added only when a listener needs
them, to keep the catalog meaningful: there is currently no user.signedIn,
user.passwordResetRequested, or file.uploaded. If you need one of these,
open an issue or PR - adding an event is a one-line emit plus an entry in the
VitNodeEvents map. (user.deleted is a special case: it is declared in the
map already, but waits on core growing a user-deletion flow.)