npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@planningcenter/core-automations

v1.17.0

Published

Display and manage core automations

Readme

@planningcenter/core-automations

A package for implementing core automations in a product.

How does it work?

Core automations are built on top of Webhooks. Automation definitions are stored in the Webhooks database, and this package of React components creates/reads/updates/deletes them via the Webhooks API. Any message bus event can be registered as a trigger for an automation, and an automation can run any pco-people-list Operation.

Usage

Implementing core automations in a product requires the following:

  1. 🚌 Add your event triggers to the message bus
  2. 🙋 Tell Webhooks how to parse your event messages
  3. 🔐 Build an encrypted parameter to secure the automations requests from your product to Webhooks
  4. 🖼️ Install and render the <Automations> components in your product

1. 🚌 Add your event triggers to the message bus

Automations are triggered by message bus messages, so make sure you're broadcasting the appropriate messages.

ℹ️ Example: Adding Groups memberships to the bus

2. 🙋 Tell Webhooks how to parse your event messages

When Webhooks receives the trigger message, it needs to know how to:

  1. extract the person_id from the message payload
  2. build a URL to the page where the automation components live (for notifications)

You'll need to open a PR into Webhooks to add new products/trigger events.

ℹ️ Example: Teach Webhooks to handle automations triggered by groups events

3. 🔐 Build the encrypted parameters to secure the automations requests from your product to Webhooks

Trigger resource

All requests to the Webhooks Automations APIs must include this encrypted parameter. Webhooks uses this to authorize the requests.

Example from Groups:

module GroupAutomationsHelper
  def encrypted_group_trigger_resource(group)
    trigger_resource = {
      # The resource that owns the automations (e.g. the Group/Form/List)
      trigger_resource: "groups/v2/groups/#{group.id}",
      # The person making the requests
      requested_by_id: Person.current.account_center_id,
      # Used to prevent replaying old messages
      time_of_request: Time.current,
      # Can this person edit these automations?
      updates_permitted: policy(group).edit?
    }

    PCO::URL::Encryption.encrypt(trigger_resource.to_json)
  end
end

ℹ️ Example: encrypted_group_trigger_resource

Incoming automations

If you want to show automations that target this resource as well as ones that originate here, you'll need another parameter that describes how to find such automations.

Example:

def encrypted_group_incoming_automations_param(group)
  operation_params = {
    operation_app_name: "groups",
    # Used to find automations that target our resource (the group, this case).
    # Needs to be a subset of the automation's `operation_params`.
    operation_params: { group_id: group.id },
    requested_by_id: Person.current.account_center_id,
    time_of_request: Time.current
  }

  PCO::URL::Encryption.encrypt(operation_params.to_json)
end

4. 🖼️ Install and render the <Automations> components in your product

ℹ️ Example: Core automations in Groups

Installation

yarn add @planningcenter/core-automations

Peer dependencies: Core automations requires that react, react-dom, and @planningcenter/tapestry-react be installed in your product.

Rendering the Automations components

import { Automations } from "@planningcenter/core-automations"

...

<Automations
  blankStateDescription="Automations supercharge your form..."
  currentOrganization={{
    dateFormat: "%m/%d/%Y",
    olsonTimeZone: "America/Los_Angeles",
    twentyFourHourTime: false,
  }}
  currentPersonCanCreate={true}
  currentPersonId={123456789}
  defaultApp="people"
  incomingAutomationsParam="..."
  theme={theme} // Tapestry-React theme object
  trigger={{
    conditions: { data: { relationships: { form: { data: { id: form.id } } } } },
    events: [{
      description: 'Submits this form',
      name: 'people.v2.events.form_submission.created',
    }],
    resource: "vzfnx3tc87lx1c1d8Ak1clkrwqZfnA294t...",
  }}
/>

Props

  • blankStateDescription (string): Text to be displayed when there are no current automations
  • blankStateLink (string) optional: A URL for the link to "Learn more", that will display below the blankStateDescription when there are no current automations. It defaults to the link to this zendesk article.
  • currentOrganization (object): Date formatting info from the current org:
    • dateFormat (string): The org's desired date format, in Ruby strftime format
    • olsonTimeZone (string): The org's time zone
    • twentyFourHourTime (boolean): Whether the org wants to see 12 or 24 hour times
  • currentPersonCanCreate (boolean): Can the user create an automation?
  • currentPersonId (number): ID of current user
  • defaultApp (string): Which app should be selected as the default target when creating a new automation?
  • incomingAutomationsParam (string): The encrypted incoming automations param (from step 3)
  • theme (object) optional: Accepts a Tapestry-React theme object
  • trigger (object): ℹ️ You can pass in null for the trigger if you only need the Incoming Automations section to render.
    • conditions (object): The specific conditions for triggering this automation: Should be a subset of the message payload
    • dynamicConditions (DefinitionField[]): An array of extra fields that can be used to customize the trigger conditions
    • events (object[]): An array of objects describing the possible trigger events (created/deleted/etc)
      • name (string): The event name (matches the webhooks message routing_key)
      • description (string): Human-readable description of this event (the words after "When a person...", e.g. "Joins this group")
      • conditions (object) optional: The specific conditions for triggering this automation: Should be a subset of the message payload
      • dynamicConditions (DefinitionField[]) optional: An array of extra fields that can be used to customize the trigger conditions
      • triggerParams (object[]) optional: An array of objects describing additional parameters which the trigger can use for determining whether to run etc.
        • key (string): The parameter key
        • defaultValue (any): What the parameter value defaults to
        • inputType ("checkbox"): Currently only "checkbox" is supported
        • description (string): The text which will be shown in the UI for the admin ("Also apply to joint donors?" for instance)
    • resource (string): The encrypted trigger resource param from Step 3 (👆)
  • afterCreateOptions (object) optional: Show a custom checkbox to optionally perform an action after the automation is created
    • label (string): Description of the action to perform (e.g. "apply to everyone on this list")
    • onConfirm (function): The function that will perform after the automation is created
  • permissionDeniedReason (string) optional: Message to display when current person is unable to create an automation

Conditional response triggers

Trigger events can carry response data (e.g. form submission answers). Pairing dynamicConditions with the MongoDB-style filters supported in Webhooks lets a host build a trigger UI that filters on those response values — for example, "fire only when the Campus field is selected as Carlsbad."

ℹ️ Example: Conditional field response triggers for People forms, built on the Calendar precursor and the core-automations plumbing that added renderOnly, "Hidden", and chain-walking renderIf.

The pattern

The People form-submission flow is the worked example. It's a nested-Select chain plus a Hidden discriminator:

  1. A top-level toggle (e.g. "All submissions" vs. "Specific responses") — a Select marked renderOnly: true. It gates everything downstream via renderIf, but its own value never writes to trigger_conditions.
  2. A Field: Select listing the form's fields. Also renderOnly: true, with a renderIf gating it on the toggle.
  3. A leaf Response: Select listing that field's options. This is the only field that writes into trigger_conditions. Marked required: true so Create stays disabled until the user picks a value — without this, picking a field and leaving the leaf on "Select a response" would produce an unfiltered automation that fires for every submission while the UI implied a response filter.
  4. A Hidden field with a defaultValue for any fixed filter key the branch needs (e.g. a data.type discriminator). Gated by its own renderIf chain so it only fires when the branch is active.

Because renderIf walks the full ancestor chain, toggling back to "All submissions" cleanly removes every downstream selection from trigger_conditions.

ℹ️ Example: Forms::FormOperationsHelper#form_automation_dynamic_conditions — the host-side helper that builds this chain for People.

renderOnly fields are deliberately excluded from disableSubmit; only required gates Create. See 205e6d5 for the rationale.

Response labelers

When the trigger fires, the resulting automation card shows a human-readable suffix derived from the saved trigger_conditions — e.g. "…with Campus selected as Carlsbad". The ids inside trigger_conditions are translated into labels by a response labeler, dispatched by the subscription's name (the message bus routing key, e.g. people.v2.events.form_submission.created). Each trigger source owns the shape of its own trigger_conditions and the labeler that decodes it.

Files live in src/utils/response-labelers/. The dispatch registry is src/utils/resolve-response-details.ts, and the interface is:

type ResponseLabeler = {
  resolve(subscriptions: AutomationInstance[]): Promise<AutomationInstance[]>
}

A labeler returns each input subscription with responseDetails: { fieldLabel, valueLabel } attached. valueLabel: null means "render the field name only" — used for booleans, where the renderer picks the wording ("checked" / "yes" / "no").

Labelers must gracefully degrade when the viewer lacks access to a referenced resource. The People labeler permission-gates form/field/option/campus lookups against the existing API edges and falls back to [Hidden Field] / [Hidden Response] strings, so the same component renders for both fully-permitted viewers and partial-access viewers without any special-casing at the call site.

To add a labeler for a new trigger source:

  1. Add src/utils/response-labelers/<your-source>.ts exporting a ResponseLabeler.
  2. Register it in src/utils/resolve-response-details.ts under the subscription name your trigger source emits.
  3. Make sure the trigger_conditions payload your host writes is something your labeler can decode end-to-end. See src/utils/response-labelers/people-form.ts as a reference.

ℹ️ Example: Resolve form field/option labels live — the labeler dispatch and the People form labeler.

Theming

Each host app can pass in a Tapestry-React theme file to the theme prop to customize the colors and styles of the component. Although some UI elements will remain consistent across all apps, the colors and styles of <Button>s and <Link>s will be directly affected by the theme oject passed in. If none is passed in, the default Tapestry-React theme will be used.

If your app is already using Tapestry-React, you can simply reuse whatever you normally pass to ThemeProvider. If your app is not currently using Tapestry-React, you can create a simple theme object that defines the primary colors to be used. Although there are plenty of other values that can be defined, the following colors that start with primary* are the most important.

  const theme = {
    colors: {
      primary: "#4076e2",
      primaryLight: "#6590e7",
      primaryLighter: "#adc3f0",
      primaryLightest: ...,
      primaryDark: ...,
      primaryDarker: ...,
      primaryDarkest: ...,
    }
  }

For more information about theming in Tapestry-React, see https://planningcenter.github.io/tapestry-react/theming.

Development

Storybook is installed for documentation and a quick development feedback loop. It runs at people.pco.test:6006 by default so that pco-api session auth works automatically.

Troubleshooting

  • If the modals are visually cut off when they are rendered, check to see if any elements higher in the DOM are using the transform property. This property creates a "new local coordinate system" which will affect the positioning of the modal and its overlay.

Contributing

If you'd like to contribute, you can find details for getting started in the contribution guide.