What you'll learn
  • what are lifecycle events
  • how lifecycle events work
  • how to subscribe to a lifecycle event

Lifecycle events using publish/subscribe pattern replace the PagePlugin hooks starting from version 5.20.0.

Overview
anchor

In our Page Builder we provide lifecycle events available for you to hook into. Lifecycle events are triggered before (onBefore keyword) and after (onAfter keyword) the data is stored into the database.

With onBefore events you can change the data that is being stored into the database, so be careful with that.

With the lifecycle events you can hook into a number of different operations, for example:

  • change the page data which is going to be stored
  • notify another system that new page was stored

System
anchor

onBeforeInstall
anchor

This event is triggered before the installation of the Page Builder and insertion of initial Welcome to Webiny and Not Found pages.

Event Arguments
anchor

PropertyDescription
tenantID of the current tenant

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeInstall.subscribe(async ({ tenant }) => {
    await notifyAnotherSystemThatPageBuilderIsGettingInstalled({ tenant });
  });
});

onAfterInstall
anchor

This event is triggered after the installation of the Page Builder and insertion of initial Welcome to Webiny and Not Found pages.

Event Arguments
anchor

PropertyDescription
tenantID of the current tenant

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterInstall.subscribe(async ({ tenant }) => {
    await notifyAnotherSystemThatPageBuilderWasInstalled({ tenant });
  });
});

Settings
anchor

onBeforeSettingsUpdate
anchor

This event is triggered before settings data is going to be stored.

Event Arguments
anchor

PropertyDescription
originalSettings object which was received from the database
settingsSettings object which was changed by user input
metaMetadata
meta.diff.pagesArray which contains which pages were changed

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeSettingsUpdate.subscribe(async ({ meta }) => {
    await triggerPreparePrerenderingOfChangedPages({ pages: meta.diff.pages });
  });
});

onAfterSettingsUpdate
anchor

This event is triggered after settings data was stored.

Event Arguments
anchor

PropertyDescription
originalSettings object which was received from the database
settingsSettings object which was changed by user input
metaMetadata
meta.diff.pagesArray which contains calculated page changes

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterSettingsUpdate.subscribe(async ({ original, settings, meta }) => {
    await storeSettingsToAnotherSystem({ original, settings });
    await triggerPrerenderingOfChangedPages({ pages: meta.diff.pages });
  });
});

Categories
anchor

onBeforeCategoryCreate
anchor

This event is triggered before new category is stored into the database.

Event Arguments
anchor

PropertyDescription
categoryCategory object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeCategoryCreate.subscribe(async ({ category }) => {
    /**
     * For example, you do not want the category name to contain the character "a".
     * You can check for that character here and throw an error. Or remove it.
     */
    if (category.name.match(/a/) === null) {
      return;
    }
    category.name = category.name.replace(/a/g, "");
  });
});

onAfterCategoryCreate
anchor

This event is triggered after new category is stored into the database.

Event Arguments
anchor

PropertyDescription
categoryCategory object which was stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterCategoryCreate.subscribe(async ({ category }) => {
    await storeCategoryToAnotherSystem({ category });
  });
});

onBeforeCategoryUpdate
anchor

This event is triggered before existing category is updated and stored.

Event Arguments
anchor

PropertyDescription
originalCategory object which was received from the database
categoryCategory object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeCategoryUpdate.subscribe(async ({ original, category }) => {
    /**
     * For example, you do not want to allow category slug changes.
     */
    if (original.slug === category.slug) {
      return;
    }
    throw new Error(`You are not allowed to change the category slug.`);
  });
});

onAfterCategoryUpdate
anchor

This event is triggered after existing category is updated and stored.

Event Arguments
anchor

PropertyDescription
originalCategory object which was received from the database
categoryCategory object which was stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterCategoryUpdate.subscribe(async ({ original, category }) => {
    await storeCategoryToAnotherSystem({ original, category });
  });
});

onBeforeCategoryDelete
anchor

This event is triggered before category is deleted from the database.

Event Arguments
anchor

PropertyDescription
categoryCategory object which is going to be deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeCategoryDelete.subscribe(async ({ original, category }) => {
    /**
     * For example, we do not want to allow any category with a name which contains character "b" to be deleted.
     */
    if (category.name.match(/b/) === null) {
      return;
    }
    throw new Error(`You are not allowed to delete a category with charcter "b" in its name.`);
  });
});

onAfterCategoryDelete
anchor

This event is triggered after category is deleted from the database.

Event Arguments
anchor

PropertyDescription
categoryCategory object which was deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterCategoryDelete.subscribe(async ({ original, category }) => {
    await deleteCategoryFromAnotherSystem({ original, category });
  });
});

Menus
anchor

onBeforeMenuCreate
anchor

This event is triggered before new menu is stored into the database.

Event Arguments
anchor

PropertyDescription
inputInput received from user
menuMenu object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeMenuCreate.subscribe(async ({ menu }) => {
    /**
     * For example, do not allow empty menu description.
     */
    if (menu.description) {
      return;
    }
    throw new Error(`Empty menu description is not allowed.`);
  });
});

onAfterMenuCreate
anchor

This event is triggered after new menu is stored into the database.

Event Arguments
anchor

PropertyDescription
inputInput received from user
menuMenu object which was stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterMenuCreate.subscribe(async ({ menu }) => {
    await notifyAnotherSystemAboutNewMenu({ menu });
  });
});

onBeforeMenuUpdate
anchor

This event is triggered before existing menu is changed and stored.

Event Arguments
anchor

PropertyDescription
inputInput received from user
originalMenu object which was received from the database
menuMenu object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeMenuUpdate.subscribe(async ({ menu }) => {
    /**
     * For example, do not allow empty menu description.
     */
    if (menu.description) {
      return;
    }
    throw new Error(`Empty menu description is not allowed.`);
  });
});

onAfterMenuUpdate
anchor

This event is triggered after existing menu is changed and stored.

Event Arguments
anchor

PropertyDescription
inputInput received from user
originalMenu object which was received from the database
menuMenu object which was stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterMenuUpdate.subscribe(async ({ original, menu }) => {
    await notifyAnotherSystemAboutMenuUpdate({ original, menu });
  });
});

onBeforeMenuDelete
anchor

This event is triggered before existing menu is deleted from the database.

Event Arguments
anchor

PropertyDescription
menuMenu object which is going to be deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforeMenuDelete.subscribe(async ({ menu }) => {
    /**
     * For example, do not allow menu with non-empty description to be deleted.
     */
    if (!menu.description) {
      return;
    }
    throw new Error(`Menu with non-empty description cannot be deleted.`);
  });
});

onAfterMenuDelete
anchor

This event is triggered after existing menu is deleted from the database.

Event Arguments
anchor

PropertyDescription
menuMenu object which was deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterMenuDelete.subscribe(async ({ menu }) => {
    await notifyAnotherSystemAboutMenuDelete({ menu });
  });
});

Page Elements
anchor

onBeforePageElementCreate
anchor

This event is triggered before new page element is stored into the database.

Event Arguments
anchor

PropertyDescription
pageElementPage element object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageElementCreate.subscribe(async ({ pageElement }) => {
    /**
     * For example, change the default generated ID to uuid v4
     */
    pageElement.id = uuidv4();
  });
});

onAfterPageElementCreate
anchor

This event is triggered after new page element is stored into the database.

Event Arguments
anchor

PropertyDescription
pageElementPage element object which is stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageElementCreate.subscribe(async ({ pageElement }) => {
    await notifyAnotherSystemAboutNewPageElement({ pageElement });
  });
});

onBeforePageElementUpdate
anchor

This event is triggered before existing page element is changed and stored.

Event Arguments
anchor

PropertyDescription
originalPage element object which was received from the database
pageElementPage element object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageElementUpdate.subscribe(async ({ original, pageElement }) => {
    /**
     * For example, do not allow id to be changed
     */
    if (original.id === pageElement.id) {
      return;
    }
    throw new Error(`You cannot change the ID of the page element.`);
  });
});

onAfterPageElementUpdate
anchor

This event is triggered after existing page element is changed and stored.

Event Arguments
anchor

PropertyDescription
originalPage element object which was received from the database
pageElementPage element object which is stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageElementUpdate.subscribe(async ({ original, pageElement }) => {
    await notifyAnotherSystemAboutPageElementUpdate({ original, pageElement });
  });
});

onBeforePageElementDelete
anchor

This event is triggered before page element is deleted from the database.

Event Arguments
anchor

PropertyDescription
pageElementPage element object which is going to be deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageElementDelete.subscribe(async ({ pageElement }) => {
    /**
     * For example, do not allow ANY page element to be deleted.
     */
    throw new Error(`You cannot delete page element.`);
  });
});

onAfterPageElementDelete
anchor

This event is triggered after page element is deleted from the database.

Event Arguments
anchor

PropertyDescription
pageElementPage element object which was deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageElementDelete.subscribe(async ({ pageElement }) => {
    await notifyAnotherSystemAboutPageElementDelete({ pageElement });
  });
});

Pages
anchor

onBeforePageCreate
anchor

This event is triggered before a new page is stored into the database. This event is not triggered when creating a page from another page.

Event Arguments
anchor

PropertyDescription
pagePage object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageCreate.subscribe(async ({ page }) => {
    /**
     * For example, set default title.
     */
    page.title = `Page created on ${new Date().toISOString()}`;
  });
});

onAfterPageCreate
anchor

This event is triggered after new page is stored into the database.

Event Arguments
anchor

PropertyDescription
pagePage object which was stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageCreate.subscribe(async ({ page }) => {
    /**
     * For example, notify another system that a new page was created.
     */
    await notifyAnotherSystemAboutNewPage({ page });
  });
});

onBeforePageCreateFrom
anchor

This event is triggered before a new page, which is created from another page, is stored into the database.

Event Arguments
anchor

PropertyDescription
originalPage object which is the base for a new page
pagePage object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageCreateFrom.subscribe(async ({ original, page }) => {
    /**
     * For example, change the title of the page so user knows it's a clone.
     */
    page.title = `Page is a clone of ${original.title}`;
  });
});

onAfterPageCreateFrom
anchor

This event is triggered after a new page, which is created from another page, is stored into the database.

Event Arguments
anchor

PropertyDescription
originalPage object which is the base for a new page
pagePage object which was stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageCreateFrom.subscribe(async ({ original, page }) => {
    /**
     * For example, notify another system that a new page was created from the original one.
     */
    await notifyAnotherSystemAboutClonedPage({ original, page });
  });
});

onBeforePageUpdate
anchor

This event is triggered before a page is changed and stored into the database.

Event Arguments
anchor

PropertyDescription
originalPage object which was received from the database
pagePage object which is going to be stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageUpdate.subscribe(async ({ original, page }) => {
    /**
     * For example, check if the page path is unique and if it's not, add some random string at the end of the path.
     */
    const unique = await yourCustomIsUniqueFunction(page);
    if (unique) {
      return;
    }
    page.path = `${page.path}-${yourRandomStringGenerator()}`;
  });
});

onAfterPageUpdate
anchor

This event is triggered after a page changed and stored into the database.

Event Arguments
anchor

PropertyDescription
originalPage object which was received from the database
pagePage object which was stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageUpdate.subscribe(async ({ original, page }) => {
    /**
     * For example, notify another system that a page was changed and stored.
     */
    await notifyAnotherSystemAboutPageUpdate({ original, page });
  });
});

onBeforePageDelete
anchor

This event is triggered before a page is deleted from the database.

Event Arguments
anchor

PropertyDescription
pagePage object which is going to be deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageDelete.subscribe(async ({ page }) => {
    /**
     * For example, we do not allow a page which has "index" set as path to be deleted
     */
    if (page.path !== "index") {
      return;
    }
    throw new Error(`Page with path "index" cannot be deleted.`);
  });
});

onAfterPageDelete
anchor

This event is triggered after a page is deleted from the database.

Event Arguments
anchor

PropertyDescription
pagePage object which was deleted

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageDelete.subscribe(async ({ page }) => {
    /**
     * For example, notify another system that a page was deleted.
     */
    await notifyAnotherSystemAboutPageDelete({ page });
  });
});

onBeforePagePublish
anchor

This event is triggered before a page is changed and stored into the database as the published one.

Event Arguments
anchor

PropertyDescription
publishedPagePage object that is set as the published one in the database - published revision of page we are publishing
latestPagePage object of the last revision of the page we are publishing
pagePage object which is going to be published

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePagePublish.subscribe(async ({ latestPage, page }) => {
    /**
     * For example, we do not allow a page which is not the latest one to be published.
     */
    if (latestPage.version > page.version) {
      throw new Error(`Page you are trying to publish is not the latest revision of the page.`);
    }
  });
});

onAfterPagePublish
anchor

This event is triggered after a page is changed and stored into the database as the published one.

Event Arguments
anchor

PropertyDescription
publishedPagePage object that is set as the published one in the database - published revision of page we are publishing
latestPagePage object of the last revision of the page we are publishing
pagePage object which was published

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPagePublish.subscribe(async ({ publishedPage, latestPage, page }) => {
    /**
     * For example, notify another system of a published page.
     */
    await notifyAnotherSystemAboutPublishedPage({ publishedPage, page });
  });
});

onBeforePageUnpublish
anchor

This event is triggered before a page is changed and stored into the database.

Event Arguments
anchor

PropertyDescription
latestPagePage object of the last revision of the page we are unpublishing
pagePage object which is going to be unpublished

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageUnpublish.subscribe(async ({ latestPage, page }) => {
    /**
     * For example, we do not allow a page which is the latest one to be unpublished.
     */
    if (latestPage.version === page.version) {
      throw new Error(
        `Page you are trying to unpublish is the latest revision of the page and it is not allowed to unpublish it.`
      );
    }
  });
});

onAfterPageUnpublish
anchor

This event is triggered after a page is changed and stored into the database.

Event Arguments
anchor

PropertyDescription
latestPagePage object of the last revision of the page we are unpublishing
pagePage object which was unpublished

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageUnpublish.subscribe(async ({ page }) => {
    /**
     * For example, notify another system that page was unpublished.
     */
    await notifyAnotherSystemAboutUnpublishedPage({ page });
  });
});

onBeforePageRequestChanges
anchor

This event is triggered before a page is marked as Requested Changes status and stored into the database.

Event Arguments
anchor

PropertyDescription
latestPagePage object of the last revision of the page we are requesting changes on
pagePage object which is going to be set into Requested Changes status

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageRequestChanges.subscribe(async ({ page }) => {
    /**
     * For example, we will check another system if request changes can be done on this page.
     */
    const allowed = await canRequestChangesBeDone({ page });
    if (allowed) {
      return;
    }
    throw new Error(`You cannot request changes on this page.`);
  });
});

onAfterPageRequestChanges
anchor

This event is triggered after a page is marked as Requested Changes status and stored into the database.

Event Arguments
anchor

PropertyDescription
latestPagePage object of the last revision of the page we are requesting changes on
pagePage object which was set into Requested Changes status and stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageRequestChanges.subscribe(async ({ page }) => {
    /**
     * For example, notify another system that a request changes was set on this page.
     */
    await notifyAnotherSystemAboutRequestChanges({ page });
  });
});

onBeforePageRequestReview
anchor

This event is triggered before a page is marked as Requested Review status and stored into the database.

Event Arguments
anchor

PropertyDescription
latestPagePage object of the last revision of the page we are requesting review on
pagePage object which is going to be set into Requested Review status

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onBeforePageRequestReview.subscribe(async ({ page }) => {
    /**
     * For example, we will check another system if request review can be done on this page.
     */
    const allowed = await canRequestReviewBeDone({ page });
    if (allowed) {
      return;
    }
    throw new Error(`You cannot request review on this page.`);
  });
});

onAfterPageRequestReview
anchor

This event is triggered after a page is marked as Requested Review status and stored into the database.

Event Arguments
anchor

PropertyDescription
latestPagePage object of the last revision of the page we are requesting review on
pagePage object which was set into Requested Review status and stored

How to Subscribe to This Event?
anchor

new ContextPlugin<PbContext>(async context => {
  context.pageBuilder.onAfterPageRequestReview.subscribe(async ({ page }) => {
    /**
     * For example, notify another system that a request review was set on this page.
     */
    await notifyAnotherSystemAboutRequestReview({ page });
  });
});

Please, be aware that you can change what ever you want on the object before it is stored into the database, so be careful with changing the data.

Registering Lifecycle Event Subscriptions
anchor

For the subscriptions (your code) to be run, you must register it in the createHandler in the api/code/graphql/src/index.ts file.

api/code/graphql/src/index.ts
const handler = createHandler({
  plugins: [
    // ... rest of plugins
    new ContextPlugin<PbContext>(async context => {
      context.pageBuilder.onBeforePageCreate.subscribe(async ({ page }) => {
        // do your magic here
      });
    })
  ]
});

Please, be aware that the order of subscribing matters, so if you want some event subscription to be executed before some other one, add it first.