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.

EventPayloadEmitted 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").

Example: welcome email listener
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:

Example: clean up plugin data on role deletion
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.)

On this page