Plugin API

What is a plugin?

Pitchly apps take many different forms. Most of the time, apps exist outside of the Pitchly platform and either make API requests on behalf of the current user or on behalf of the organization that owns the app. But there is another type of app: what we call a "plugin".

A plugin is an app that sits side-by-side with the Pitchly platform in the same window, as shown in the example below. In this example, the Unsplash app allows users to search for stock photos and insert them directly into their table.

The benefit of making your app a plugin inside of Pitchly is that your app can intimately interact with the user's data tables in real time while the user is working inside Pitchly. This is advantageous for data-first workflows, where you expect the user to primarily work inside their database but you would like to extend their workflow with some functionality provided by your app.

The example below shows a more sophisticated workflow with our Elements app, where as the user filters their data and selects records, the elements in the right pane change to reflect the current view and selection. The reverse is also true: when the user selects elements in the right pane, the records are selected in the table on the left.


In order to serve your app as a plugin to Pitchly users, your interface must be publicly accessible at a specific URL you host. You will need to provide us with that URL and let us know that you want to display it as a plugin. When users install your app and click on its button to the right of their workspace, we will load your plugin URL in an iFrame.


Plugins interact with the platform via a series of postMessage events. In order to display your plugin and start receiving postMessage events from the platform, an initial handshake must first be performed:

  1. When your app is ready to receive messages, the app must send Pitchly the ready message.

  2. In response to the ready message, Pitchly will send your app multiple messages indicating the current state of the UI.

  3. As the user interacts with Pitchly, your app will continue to receive individual messages when there are changes to the UI state.

Important: If your plugin is stuck on the loading page and not displaying, most likely Pitchly hasn't received the ready message from your plugin. We will only display your plugin once we receive the ready message, and we will additionally send back the current UI state when we receive the ready event. This handshake must be performed because Pitchly can't otherwise predict when your app is ready to receive events.

To send the ready event, simply call:

window.parent.postMessage({ type: "ready" }, "");

The second parameter ensures that you only send this message to Pitchly and no other websites that may have iFramed your plugin interface. It's important that you include this in order to keep your cross-iFrame communication secure.

On Pitchly's side, we similarly only send messages to the same origin as the plugin URL you originally gave us. This means if you redirect the user inside your iFrame to another origin, you will not be able to receive our postMessages.


Most commonly, when an event happens inside Pitchly, such as a filter being set or a record selection being made, this may trigger something to change inside your plugin's UI. You may expect that when a record is selected, for example, you will get the entire record data delivered to your app via postMessage. However, passing entire record data is not only inefficient but a potential security risk, as we don't yet have proof that your plugin should receive access to this user's data, and we don't want to trust only the browser to make this confirmation.

For this reason, postMessages themselves do not typically contain any sensitive information. You will normally receive only IDs to things, such as records, tables, the current user, etc. At most, you may receive the strings users have typed into filter conditions, for example.

In order to receive the actual data behind these IDs, you must make requests to our GraphQL API, just as you would if your app lived outside of Pitchly. For that to work, you must first possess proof that you have authorized access to this user's data. You do so by obtaining an access token.

Most of the time, the per-user OAuth flow is most appropriate for plugins because the access it offers aligns with the current user who is using your plugin. After redirecting the user to the authorization endpoint and completing the OAuth flow, you can use the resulting access token to make API requests, informed by the postMessage events you receive.

How to receive and send messages

To receive messages from Pitchly into your plugin, you will need to create a message event handler:

window.addEventListener("message", function(event) {
  // Verify the message came from Pitchly
  if (event.origin!=="") return;
  const message =;
  // Replace saveInLocalState with your own function to save message data.
  // Messages from Pitchly are designed to work consistently with reactive
  // frameworks, so you can expect to receive several messages right after
  // you send the "ready" message to set the initial local state. Subsequent
  // events will trigger only individual messages to be sent as needed,
  // updating your local state as changes occur. Every message is guaranteed
  // to contain at least a type (string) and data (object).

To send a message to Pitchly from your plugin, simply call the following:

const message = { type: "ready" }; // replace with your message
window.parent.postMessage(message, "");

Importantly, Pitchly will only start sending your plugin messages after you send Pitchly the "ready" message. So most often, it makes sense to call your ready event right after you initialize your message listener, since your message listener is now ready to receive messages.

Below you can find a list of all messages that can be sent between your plugin and Pitchly.

Plugin -> Pitchly messages


Send this when your plugin is ready to receive messages.

{ type: "ready" }

Selected records

Send with a list of record IDs you would like to select in the current table.

If recordIds is null, indicates that a "select all" should be performed, which will select all records in the current view, not just the records showing on the current page.

To clear the current selection, recordIds should be an empty array.

{ type: "selectedRecords", data: { recordIds: [] || null } }

Pitchly -> Plugin messages

Current organization

Gets sent with the ID of the current organization.

{ type: "organization", data: { organizationId: null } }

Current person

Gets sent with the ID of the current person logged in.

{ type: "person", data: { personId: null } }

Current workspace

Gets sent with the ID of the current workspace.

{ type: "workspace", data: { workspaceId: null } }

Current table

Gets sent with the ID of the current table being viewed.

{ type: "table", data: { tableId: null } }

Current table ready

Gets sent with a boolean indicating whether the current table is "ready".

{ type: "tableReady", data: { isTableReady: false } }

NOTE: This differs from the ready event that is sent to Pitchly to indicate the plugin is ready to receive messages. The main purpose of this "tableReady" event is to indicate whether the user has marked this table as "ready" to be accessible by non-admins and apps in the workspace. The intention behind this functionality is to provide admins with the opportunity to configure a table's "default view" before the table is made available more broadly. When a table isn't ready, your plugin will be unable to get data about the table from the GraphQL API, but this event is still provided so that you can accommodate in the UI by, for example, showing a banner to tell the user their table hasn't been marked as "ready" yet.

Current view

Gets the current view configuration representing the view of the data currently being displayed on the screen. Note that this does not represent a view being selected necessarily, but instead represents the current view config. So if the user changes filter or sort criteria manually, for example, you will receive another updated view representing that change in state.

When not empty, each property will be an object representing the associated item.

{ type: "view", data: { filter: null, sort: null, visibleFields: null, fieldOrder: null, columnWidths: null } }

Currently active data cell

Gets sent with the field and record ID of the currently active data cell (the cell highlighted with a border around it).

{ type: "activeCell", data: { recordId: null, fieldId: null } }

If there are no records in the current view, recordId and fieldId will both be null. If at least one data cell becomes available again, the first data cell will automatically become active again.

Selected records

Gets sent with a list of record IDs the user has selected in the current table.

If recordIds is null, indicates that a "select all" has been performed, which will select all records in the current view, not just the records showing on the current page.

recordIds will be an empty array if no selection is made.

{ type: "selectedRecords", data: { recordIds: [] || null } }

Record pagination

Gets sent with before or after cursor indicating where the current records data begins for the purposes of pagination.

{ type: "recordPagination", data: { before: null, after: null } }

Plugin focus state

Gets sent when the plugin becomes visible (isFocused true) or hidden (isFocused false). Note that when a plugin is closed in Pitchly, it may remain running in the background and continue to receive postMessages. Detecting the focus state is useful for doing something when the user returns to the plugin, or if you want to stop certain tasks from running while the plugin isn't visible.

{ type: "focus", data: { isFocused: true } }

Security precautions

One potential edge case to consider when displaying your plugin in Pitchly is that, sometimes, the Pitchly account the user used to log into your plugin may not match the account they're currently logged in with in Pitchly. You can easily check this scenario by verifying whether the personId we sent you matches the ID of the Person currently logged into your plugin. If they do not match, then you should log the user out of their Pitchly account in your plugin and have them re-login.

Although this isn't a security risk in the traditional sense of giving an unauthorized user access to your user's data, what could happen is one of the user's accounts could inadvertently share data about another account that the same physical user has also logged in with. While confidential data would not be shared, information such as filters used, view criteria, etc. could be shared just by the nature of how postMessages work and deliver information across frames.

While this is only a problem if the same user has multiple accounts, it is best to avoid this scenario altogether by simply checking whether the accounts between your plugin and Pitchly match, and automatically log the user out if they don't.

Note that if you keep receiving errors such as "You do not have access to this resource" from your API calls when making GraphQL queries about the IDs you're receiving via postMessages, being logged into Pitchly and your plugin with different accounts can often be the cause.

Last updated