Introduction

ZEIT Integrations introduce a way to extend the capabilities of ZEIT and to connect with any platform.

The following is just some the things you can do with ZEIT Integrations:

  • Add databases into deployments with just a few clicks.
  • Add custom UI elements to the ZEIT Dashboard.
  • Complete access to the ZEIT API.
  • Configure third-party services such as error tracking and performance monitoring.
  • Create mashup apps by connecting multiple different services of your choosing.
  • Access hardware and connect with external devices such Arduino, Philips Hue, etc.

These are just a few of the possibilities afforded by ZEIT Integrations. This page will walk you through on how to create your own to do more with ZEIT.

Hint: This page is dedicated to explaining what Integrations are and how you can develop your own. To install an Integration instead, go to the Integrations Marketplace.

Why Build or Use an Integration?

Integrations are perfect for you if you want to pair ZEIT's functionality with other services or want to extend ZEIT's functionality itself.

An example of this is the Google Cloud Integration which connects projects on ZEIT to Google Cloud Platform projects where you can run Now deployments as CRON jobs through the Google Cloud Scheduler, Create and connect to MySQL/PostgreSQL databases with Google Cloud SQL, and much more that would be a lot harder without an Integration.

Many more Integrations that help users in similar ways can be created using the ZEIT Integrations API in partnership with the ZEIT API and any external service with an API.

What Makes an Integration?

Before your start building your first Integration, the following is an explanation of what an Integration is built of:

  • A HTTP endpoint that receives a ZEIT API token, written in any language, called a UIHook.
  • It can store data such as user configuration and tokens in the Metatdata Store.
  • It can return a string of HTML, using ready-made components, as the HTTP response to build UI elements.
  • It can provide an interactive UI right inside the ZEIT Dashboard.
  • It supports OAuth 2.

With ZEIT Integrations you have opportunity to dramatically simplify the process of using your product for your customers.

Creating An Integration

A ZEIT Integration is delivered to the user through a UIHook.

A UIHook is an HTTP POST endpoint that returns a HTML string, composed of custom ready-made, or user-made, components, as the response.

The following is a walkthrough for how to make an Integration, comprising of a UIHook. This example uses the Node.js wrapper for the UIHook which is optimized for Now 2.0 deployments.

Step 1: Project Setup

Install Node.js and Now CLI

Note: If you already have the latest stable versions of Node.js and Now CLI. You can skip this step.

Firstly, download and install Node.js.

Next, you can install Now CLI through a package manager or bundled with Now Desktop with automatic updates. For more information on installation methods, see the Installation documentation.

To install Now CLI with a Yarn, use the following from the terminal:

yarn global add now

Installing Now CLI with Yarn.

Add a package.json File

We are using some npm modules inside this UIHook. So, let's create a file called package.json and the following content:

{
  "name": "my-ui-hook",
  "dependencies": {
    "@zeit/integration-utils": "latest"
  }
}

Then simply type: npm install to add dependencies.

Add a now.json File

Since this UIHook will be deployed with Now, a now.json file is used to provide minimal configuration for the deployment and how it should behave. You should also use a now.json file when running your Integration locally with now dev.

Create a now.json file at the root of your project, then add the following content:

{
  "name": "my-ui-hook",
  "version": 2,
  "builds": [{ "src": "index.js", "use": "@now/node" }]
}

An example now.json file for a ZEIT Integration.

Step 2: Creating a UIHook

This step will detail the creation of a UIHook for a counter application. This example explains the basics for how an UIHook works.

First, create an index.js file and add the following code:

const { withUiHook } = require('@zeit/integration-utils')

let count = 0

module.exports = withUiHook(({ payload }) => {
  count += 1
  return `
    <Page>
      <P>Counter: ${count}</P>
      <Button>Count Me</Button>
    </Page>
  `
})

An example index.js file for a ZEIT Integration.

Inside the UIHook there is a variable, count. Every time the Button is clicked, the UIHook is called, thus updating the counter and providing the updated HTML as a response.

This is happening inside the ZEIT Dashboard under a configuration page for this Integration.

Note: You can find more complex examples later in the documentation or you can view the code on the Integrations Gallery.

Running the UIHook Locally

With the help of now dev, you can run your UIHook locally inside a simulation of the Now platform.

By running your UIHook locally, you can test that your Integration, after creating the Integration with ZEIT, behaves as expected before deploying your Integration to production. This will be explored further in the next section.

To run the UIHook locally, use the following command:

now dev -p 5005

Running the created UIHook locally on port 5005.

Step 3: Creating Your Integration

With a UIHook created, you can now create the Integration on ZEIT.

To do this, visit the Integrations Developer Console and click the Create button.

Creating an Integration using the Integrations dashboard.

Complete the form and at the end you will find two fields named:

  • Redirect URL
  • UI Hook URL

For now, you can leave the Redirect URL empty and add http://localhost:5005 to the UI Hook URL field.

Warning: We recommend using Google Chrome for local UIHook development.

To work with UIHooks using either Safari or Firefox, you will require the use of a tunnelling service such as ngrok.

When using ngrok, the command ngrok http 5000 will provide you with an address that can be used in the UI Hook URL field.

Marketplace URL

Once you create your Integration, you can see it listed on the Integrations Developer Console.

Inside your Integration, you can click the View In Marketplace button to see it in the marketplace.

Clicking this button will take you the Public URL for your Integration. This URL can be shared with anyone who wishes to install and use your Integration.

The Public URL has the following format:

https://zeit.co/integrations/:slug

An example Public URL for an Integration.

Step 4: Add a Configuration

You can visit the Marketplace URL from the previous step and click the "Add" button to add a configuration to your installation.

Then, you can select a ZEIT account to add the configuration to:

Selecting a ZEIT account to add the configuration to.

Once added, the user will be taken to the configuration page, it will look like this:

A UIHook running inside a configuration page.

Finding Configurations

Once added, finding the configuration can be done by clicking the Integrations section through main navigation on the ZEIT Dashboard.

If you are inside a project, there is a section called Integrations as well. It will show all the configurations affecting that particular project.

When the UIHook receives a request, it contains information related to the project being viewed currently. Based on that, your UIHook can decide what HTMl components should be sent as the response.

Step 5: Testing Your Integration

Now you know how to create, develop, and add a configuration for an Integration, it's time to look at how you to test your Integration and develop it locally.

With your Integration running locally using now dev, navigate to the Integrations dashboard and select a configuration for your Integration.

You should now see your Integration working exactly as it would do if deployed, nothing else is required!

Step 6: Publishing Your Integration

We recommend you create a separate Integration to deploy into production. That way you have two Integrations, one for development and one for production.

This allows you to maintain a stable version of your Integration whilst still being free to develop new features for it in a safe environment.

To deploy your UIHook into production, all it takes is one command:

now --target=production

Deploying a UIHook to a production environment.

The output from this command will contain the alias URL for your UIHook, you should add this to your Integration in the UI Hook URL field.

Note: You can change the UIHook URL of your Integration anytime you wish.

Examples

With the previous Creating an Integration section, you should now have a clearer idea of how to create an Integration. In this section, you will find a few more examples and see what more you can do with the ZEIT Integrations.

All of these examples contains a README file showing how to run them locally, however most of them uses the following process:

  • Create an Integration using the ZEIT Dashboard
  • Set the UIHook URL to http://localhost:5005
  • Add a configuration for the Integration
  • Run the Integration's UIHook locally with now dev -p 5005
  • Access a configuration for the Integrations from the ZEIT Dashboard
  • Then you see your UIHook in action.

Counter

This is the same example as we created in Creating an Integration section. This Integration increments a counter when the user clicks a button.

Take a look at the code on GitHub.

An example of a Counter Integration.

Basic Form

This example shows how to accept user inputs and manage different actions. It allows the user to set two values and reset them using a form.

Take a look at the code on GitHub.

An example Integration to accept user inputs.

Using Metadata

This Integration is similar to the above example, however, in this case user inputs are kept in the metadata store provided by ZEIT Integrations.

Take a look at the code on GitHub.

An example Integration that accepts user inputs and stores them in the metadata store.

Connect with GitHub

This Integration uses GitHub OAuth support to connect and receive details about a user. You can use the same process for other providers who support OAuth.

Take a look at the code on GitHub.

An example Integration that connects to GitHub.

Recent Deployments

This Integration displays a list of recent deployments from your ZEIT account or related project. It fetches deployment data from the ZEIT API using the zeitClient utility.

Take a look at the code on GitHub.

An example Integration accessing the ZEIT API and render them.

More Examples

This is a just a set of basic examples showing the key functionalities of ZEIT Integrations. By combining all these features you can build some interesting Integrations.

Have a look at this collection of ZEIT Integrations supported by ZEIT and maintained with the community support. It contains Integrations for Google Cloud, Slack, LightHouse Benchmarks, MongoDB Atlas and many more.

htm Support

If you have looked at some of the example Integrations, you may have noticed the use of htm in a few places like this:

const { withUiHook, htm } = require('@zeit/integration-utils')

let count = 0

module.exports = withUiHook(({ payload }) => {
  count += 1
  return htm`
    <Page>
      <P>Counter: ${count}</P>
      <Button>Count Me</Button>
    </Page>
  `
})

An example of using htm to create a UIHook.

htm is a JavaScript tagged template which allows you to use JSX-like-Syntax for creating HTML components.

This is based on the implementation created by developit/htm.

Background

A UIHook accepts a set of inputs, such as a token or projectId, and sends back a string of HTML, including components, as the response.

The components are sent as a string. It's just like a HTML page but with a set of rich components we have defined and exposed for you to use.

Therefore, it is the role of the UIHook to create that HTML string. This is achieved through the use of string manipulation.

With JavaScript template literals, this is relatively straightforward. However, there are a few things to be aware of:

  • Escaping prop names and values
  • Escaping the content that goes into components
  • No array.map() support
  • Difficult to share common components across different Integrations and teams

Why Use htm

Using htm greatly simplifies the process of creating UIHook's, let's take a look at a basic comparison.

This is a UIHook created using a JavaScript template string:

const { withUiHook } = require('@zeit/integration-utils')

module.exports = withUiHook(({ payload }) => {
  return `
    <Page>
      Hello World
    </Page>
  `
})

An example of using a JavaScript template string to create a UIHook.

This is the same UIHook created using htm:

const { withUiHook, htm } = require('@zeit/integration-utils')

module.exports = withUiHook(({ payload }) => {
  return htm`
    <Page>
      Hello World
    </Page>
  `
})

An example of using htm to create a UIHook.

The key thing to note is the inclusion of htm just before the start of the JavaScript template string.

We have included a number of additional and more realistic use-cases in the next section.

Use Cases

As mentioned already, using htm can greatly simplify the process of creating UIHook's.

To help you understand the benefits of htm and how it differs slightly from JSX, we have created some examples below:

Variable Props

const { withUiHook, htm } = require('@zeit/integration-utils')

module.exports = withUiHook(({ payload }) => {
  const url = 'https://zeit.co'
  return htm`
    <Page>
      <Link href=${url}>ZEIT Homepage</Link>
    </Page>
  `
})

An example of using htm to provide a url prop inside a UIHook.

Variable Content

const { withUiHook, htm } = require('@zeit/integration-utils')

module.exports = withUiHook(({ payload }) => {
  const name = 'Arunoda Susirpala'
  return htm`
    <Page>
      <P>My name is ${name}</P>
    </Page>
  `
})

An example of using htm to provide variable content inside a UIHook.

Lists/Arrays

const { withUiHook, htm } = require('@zeit/integration-utils')

const urls = ['https://zeit.co', 'https://google.com']

module.exports = withUiHook(({ payload }) => {
  const name = 'Arunoda Susirpala'
  return htm`
    <Page>
      <UL>
        ${urls.map(url => htm`<LI>${url}</LI>`)}
      </UL>
    </Page>
  `
})

An example of using htm to make use of the map array method inside a UIHook.

As you can see by looking at the htm inside the urls.map function, you need to use htm in front of any JavaScript template strings containing components.

Using Shared Components

const { withUiHook, htm } = require('@zeit/integration-utils')

const urls = ['https://zeit.co', 'https://google.com']

const URL = ({ href }) => htm`
  <LI><Link href=${href} target="_blank">${href}</Link></LI>
`

module.exports = withUiHook(({ payload }) => {
  const name = 'Arunoda Susirpala'
  return htm`
    <Page>
      <UL>
        ${urls.map(url => htm`<${URL} href=${url} //>`)}
      </UL>
    </Page>
  `
})

An example of using htm to make it easier to share a component between pages inside a UIHook.

This URL component can be easily shared, whether that be within the same UIHook, another Integration or even published on npm as a module.

Creating User Interfaces

The core component of a ZEIT Integration is the UIHook. It can render different UI elements and receive inputs from the user.

Here's what a typical user UIHook looks like:

const { withUiHook } = require('@zeit/integration-utils')
const store = { secretId: '', secretKey: '' }

module.exports = withUiHook(({ payload }) => {
  const { clientState, action } = payload

  if (action === 'submit') {
    store.secretId = clientState.secretId
    store.secretKey = clientState.secretKey
  }

  if (action === 'reset') {
    store.secretId = ''
    store.secretKey = ''
  }

  return `
    <Page>
      <Container>
        <Input label="Secret Id" name="secretId" value="${store.secretId}" />
        <Input label="Secret Key" name="secretKey" type="password" value="${
          store.secretKey
        }" />
      </Container>
      <Container>
        <Button action="submit">Submit</Button>
        <Button action="reset">Reset</Button>
      </Container>
    </Page>
  `
})

As you may have noticed, a UIHook receives a set of inputs and returns a string of HTML, including components, as the response.

You can see these inputs available in the payload object mentioned in the above code. It contains a structure as shown here. We will discuss these in this section.

Components

Inside an UIHook you can use a set of whitelisted components. These components will be parsed and rendered in the ZEIT Dashboard.

For security reasons, these components have some minor limitations:

  • No HTML elements are allowed
  • Script and Style tags are not supported
  • JavaScript support is disabled
  • Only a set of whitelisted components are supported

These limitations may seem restrictive, but with very good reason.

These limitations allow us to expose a way for you to introduce UI elements into the ZEIT Dashboard without requiring manual verification.

We also provide a set of rich components that allow you to receive user inputs, implement styling and even poll for changes.

Actions

In a user interface, we usually expose multiple different actions. In the example we looked at earlier there are two actions:

<Container>
  <Button action="submit">Submit</Button>
  <Button action="reset">Reset</Button>
</Container>

An example of a UIHook using whitelisted components.

These actions are exposed as buttons, in this case submit and reset.

When a user clicks a button, we send a request to the UIHook with the action. The UIHook then sends a response in the form of a string of components, this is what will be rendered.

You can access the action from the payload.action property. The default action is view and will always be the action when the Integration is first loaded.

Note: You have complete control over the names and amount of actions created.

For a better understanding of how actions work, take a look at the Counter example discussed earlier in the documentation.

You can discover all supported action components in the component catalog.

Client State

Just like actions, you need to be able to accept multiple different inputs from the user. This might include text boxes, password prompts, dropdowns - there are many options available.

This is how we can define an input field:

<Input name="username" value="my username" />

An example of a UIHook using an Input component.

Now the user can enter a value into this input.

When the user clicks on an action, we combine all of these inputs together into an object called clientState. For this example, if just the above input was submitted, the clientState would look like this:

{
  "username": "my username"
}

Inside the UIHook you can access this object via payload.clientState.

You can discover all supported input components in the component catalog.

Information

Project Information

Inside the ZEIT Dashboard, a user can access different configurations for an Integration inside either:

  • The team or user dashboard
  • Each and every project

A UIHook can identify this by looking at the payload.project property. It can then change the user interface selectively.

Note: If the user is looking at a configurations inside the main dashboard, payload.project will be empty.

Account Information

You can access the account information of the installation inside the UIHook. Here's a list of such properties:

  • payload.user - The user who is accessing the UIHook
  • payload.team - The team of the configuration if the Integration is added into a team, otherwise empty

Tokens

Every request to a UIHook will receive a token which can be used to make requests to the ZEIT API for the related account.

This is a short-lived token. Therefore, you should only use it inside the UIHook. You should not use it inside asynchronous functions which are executed even after you return the UIHook response.

If you need a long-lived token, you can ask for a OAuth2 token.

You can access this property inside payload.token.

When you use the @zeit/integration-utils utility, you do not need to extract this token manually. You will receive an instance called zeitClient which is initialized with the above token and account information. You can use that to consume the ZEIT token.

This is how you can get that instance:

const { withUiHook } = require('@zeit/integration-utils')

module.exports = withUiHook(({ payload, zeitClient }) => {
  // Do something with the zeitClient and payload
  return `<Page>and return some JSX.</Page>`
})

You can inspect the API and methods of zeitClient to understand its capabilities.

Component Catalog

To enable the rapid development of Integrations whilst providing a safe and secure environment for all, we have created a component catalog for you to use.

The following components can be used to construct your UIHook's:

Page

This is the root of your UIHook. All other components should live inside the Page component.

<Page>Some other components</Page>

Action Components

These components create a new UIHook request with an action that forms part of the component.

Button

This is a key part of your UIHook. When the user clicks the button, we call your UIHook with the action attached to the button.

<Button action="create-db">Create Database</Button>

The Button component supports the following attributes:

  • small
  • disabled
  • width (in pixels)
  • secondary
  • warning
  • highlight
  • abort

ProjectSwitcher

This component can be used when you want to provide your users with a way of switching between projects easily, or if you require them to select a project to start using your Integration initially.

<ProjectSwitcher message="Choose a project from the list" />

The ProjectSwitcher component supports the following attribute:

  • message (default is "Select a project")

AutoRefresh

This can be use to refresh the UI by loading the UIHook again. This is useful when waiting for a background task to finish processing.

<AutoRefresh timeout="<timeout-in-millis>" />
Note: Timeouts must be greater than 3 seconds.

The AutoRefresh component supports the following attributes:

  • action
  • timeout

Link

The Link component allows you to link to either an external URL or an action:

<Link href="https://zeit.co">Visit ZEIT</Link>
<Link action="doSomething">Do Something</Link>

The Link component supports the following attribute:

  • target

Select

The Select component provides the user with a dropdown menu that contains specified options. By default, this is an input component, however, adding the action attribute, the component can be converted into an action component.

When using the action attribute, when a user selects an option from the element, the UIHook will be called again containing the defined action within the client state.

<Select name="dbName" value="selectedValue" action="change-db">
  <Option value="mongodb" caption="MongoDB" />
  <Option value="redis" caption="Redis" />
</Select>

Input Components

To receive user inputs, you should use a component appropriate for your use case.

Note: The name prop provided to the Input component will be used to populate the clientState of the UIHook and must be used if using multiple Inputs.

Input

The Input component provides the user with an input field:

<Input name="dbName" label="Name for the Database" value="the default value" />

The Input component supports the following attributes:

  • type
  • disabled
  • errored
  • value
  • name

Textarea

The Textarea component provides the user with a text area:

<Textarea name="description" label="Enter your description">
  The value
</Textarea>

The Textarea component supports the following attributes:

  • disabled
  • errored
  • width (in pixels)
  • height (in pixels)
  • name
  • label

Select

The Select component provides the user with a drop down menu that contains the specified options:

<Select name="dbName" value="selectedValue">
  <Option value="mongodb" caption="MongoDB" />
  <Option value="redis" caption="Redis" />
</Select>

Checkbox

The Checkbox component provides the user with a Checkbox input:

<Checkbox name="allowOptionA" label="Allow Option A" checked="true" />

The Checkbox component supports the following attributes:

  • name - the name of the clientState field
  • label - a label to show next to the Checkbox
  • checked - the default state of the Checkbox, either "true" or "false"

If the user checks the Checkbox, or allows it to remain in the checked state from default, clientState will have a field with the name defined in the Checkbox component with the value as true.

ClientState

The ClientState component allows you to store the current client state and retrieve it when required with an action. This is similar to an <Input /> component, except the value is set on the server side and is not visible to the user.

<ClientState key1="value1" key2="value2" />

In the above example, client state can be accessed at payload.clientState when the user triggers an action, for example, when clicking a button.

Below is an example showing the format of the clientState object:

{
  "key1": "value1",
  "key2": "value2"
}

ResetButton

The ResetButton component allows the user to reset the values of <Input /> components. Values are reset on the client side, which means that no server side request will be made.

<ResetButton targets="username, password">Reset</ResetButton>

The ResetButton component supports the following attribute:

  • targets - comma separated values of the <Input /> names. If the attribute is not defined it will reset the values of all <Input /> components.

Below is an example showing the component's usage:

<Page>
  <Input type="text" name="username" />
  <Input type="text" name="password" />
  <Input type="text" name="age" />

  <Button action="submit">Submit</Button>
  <ResetButton targets="username, password">Reset</ResetButton>
</Page>

In the above example, the username and password fields will be reset, whilst the age field will remain unchanged.

UI Components

The following UI components are very similar to those found in HTML5, the main difference being we're providing the initial styling.

Box

The Box component is a div that accepts style attributes:

<Box display="flex" justifyContent="space-between">
  <H1>Your Databases</H1>
  <Button>+Create New</Button>
</Box>

You can add as many CSS style attributes as you like.

P

<P>A paragraph.</P>

H1

<H1>The Title</H1>

H2

<H2>The Secondary Title</H2>

BR

<BR />

HR

This component renders a horizontal rule, similar to the HTML hr, styled to match ZEIT's design.

<HR />

B

<B>Some Bold Text</B>

Img

<Img src="url-to-image" />

The Img component supports the following attributes:

  • title
  • width (in pixels)
  • height (in pixels)

Code

const code = `
    <html>You can put anything here</html>
`
<Code>${code}</Code>

Fieldset

The Fieldset component allows you to group different sections in your UI.

<Fieldset>
  <FsContent>
    <H2>This is the Title</H2>
    <P>This is some content.</P>
  </FsContent>
  <FsFooter>
    <P>This is the footer.</P>
  </FsFooter>
</Fieldset>

Notice

<Notice type="error">This is an error messgae.</Notice>

The Notice component supports the following types:

  • error
  • warn
  • message
  • success

Using the Metadata Store

When installing an Integration, most of the time it will need to persist different configurations and information.

This may include but is not limited to:

  • User preferences
  • Different API secrets/tokens
  • Cached results for faster access

Usually, a database is used to persist this information, but for ZEIT Integrations, we provide a Metadata Store for this very purpose.

Every UIHook has access to a Metadata Store.

Each configuration of the Integration gets its own Metadata Store. It is limited to 100KB in size for a single configuration.

It's very common to load metadata inside the UIHook for every request. That's why we limit its size to 100KB. With that, we strike the balance between a generous amount of storage and lightning fast loading times.

If you need more storage capacity, we recommend using an external database.

Access via @zeit/integration-utils

If you use the @zeit/integration-utils helper to create the UIHook, you can access the Metadata Store via the zeitClient instance, for example:

const { withUiHook } = require('@zeit/integration-utils')

module.exports = withUiHook(async ({ payload, zeitClient }) => {
  // Get metadata
  const metadata = await zeitClient.getMetadata()
  metadata.count = metadata.count || 0
  metadata.count += 1

  // Set metadata
  await zeitClient.setMetadata(metadata)

  return `
    <Page>
    <P>Counter: ${metadata.count}</P>
      <Button>Count Me</Button>
    </Page>
  `
})

An example UIHook using the Metadata Store.

API

The following API endpoints can also be used to store metadata.

In order to use that API, you need to get the related configurationId. You can find that in the UIHook payload.

You must also provide an authorization token in the requests, further information is available here.

Get Metadata

Endpoint
GET https://api.zeit.co/v1/integrations/configuration/:configurationId/metadata

The response type is a JSON string, the structure of this is determined by the Integration's Metadata, below is en example:

{
  "user": {
    "username": "integrationuser",
    "name": "Integration User",
    "id": "id",
    "email": "mail@email.com"
  },
  "token": "token",
  "sandboxes": {
    "sandbox": "sandbox"
  }
}

An example response from the GET metadata endpoint.

Set Metadata

Endpoint
POST https://api.zeit.co/v1/integrations/configuration/:configurationId/metadata

This endpoint, using the POST method and a JSON body, sets metadata for a UIHook.

The body to submit with the POST request should be a valid JSON object containing metadata. The following is an example request using JavaScript:

await fetch("https://api.zeit.co/v1/integrations/configuration/:configurationId/metadata", {
  "method": "POST",
  "body": "{\"mydata\": \"a value for mydata\"}"
})

An example POST request to the metadata endpoint using JavaScript.

Project Level APIs

Deployments made with ZEIT Now are grouped into Projects. This means that each deployment is assigned a name and is grouped into a project with other deployments using that same name.

Users can deploy projects for any type of app. For example, there could be:

  • A project for a marketing website.
  • A project for a backend API.
  • A project for an admin dashboard.
  • A project with all three in a monorepo.

Using a ZEIT Integration, you can modify configuration for whole projects, for which we expose a set of API endpoints:

Project-Based Environment Variables

When building a ZEIT Integration, you may want to expose an API secret or a configuration URL for deployments within a project.

For example, say you want to expose the MONGO_URL environment variable; the following would be the process to do so:

  • Create a secret to store the value for MONGO_URL (This is an account wide operation). For this example, we'll assume the value for the secret key is mongo_url_for_my_db.
  • Next, add MONGO_URL=@mongo_url_for_my_db for the projects where MONGO_URL is needed.
Note: The pattern is to create a secret once per account then use it many times with different projects.

Utilities inside @zeit/integration-utils

If you are using @zeit/integration-utils to create your UIHook, the following example shows how to use utilities within your Integration:

const { withUiHook } = require('@zeit/integration-utils')

module.exports = withUiHook(async ({ payload, zeitClient }) => {
  if (payload.action === 'add-env') {
    const mongo_url = 'this is your mongo url'
    const secretName = await zeitClient.ensureSecret('mongo_url', mongo_url)
    await zeitClient.upsertEnv(payload.projectId, 'MONGO_URL', secretName)

    return `
      <Page>
        Environment variable added.
        <Button small action="view">Go Back</Button>
      </Page>
    `
  }

  return `
    <Page>
      <Button action="add-env">Add Env</Button>
    </Page>
  `
})

An example UIHook that uses @zeit/integration-utils utilities.

Based on the example above:

  • zeitClient.ensureSecret will create a unique ZEIT secret based on the hash value of mongo_url, then returns the name of secret
  • If the secret already exists, it will not be duplicated for the given value
  • The value of secretName will be, for example; mongo_url_s2kj23jh55
  • The secret is added to the project (using payload.projectId) through the zeitClient.upsertEnvmethod

Some considerations when using zeitClient.upsertEnv:

  • In order to add a project level environment variable, there should be a projectId
  • You can get the projectId from the payload object if the user is accessing the Integration installation from within a project
  • Otherwise, if the user is inside a team view, you can hide the Add Env button or ask the user to switch to accessing the Integration from within a project

Using the API directly

If you are not using @zeit/integration-utils utility, you can use the ZEIT API directly. See the following API reference documentation for how to utilize it:

Delete Hooks

When the user removes a configuration, the Integration can receive a notification via Delete Hooks. There are a variety of use-cases for Delete Hooks, for example, cleaning up resources created by the configuration.

Setting Up

The Delete Hook is a HTTP endpoint which is configured to receive HTTP DELETE requests. After you create the endpoint in your app, you can add it to the Integration via the Integration Console.

Inside your Integration's configuration page, there is an input to set the Delete Hook URL. This is where you should add the HTTP endpoint mentioned above.

With the Delete Hook URL set, when a user removes a configuration for your Integration, a HTTP DELETE request will be sent to the defined endpoint. The payload your delete endpoint receives is a JSON encoded HTTP body with the following fields:

  • configurationId
  • userId
  • teamId - null if the user does not belong to a team

Take a look at this example integration to learn more about how Delete Hooks work.

HTTP Response

You should consider this HTTP request as an event. Once you receive the request, you should schedule a task for the cleanup action.

This request has a timeout of 10 seconds. That means, if a HTTP 200 response is not received within 10 seconds, the request will be aborted.

Understanding UIHooks

A UIHook is the main component of a ZEIT Integration. In the Creating an Integration section, we used a utility from the @zeit/integration-utils module to create a UIHook.

The module we used is only available when using Node.js to build the UIHook, but a UIHook can be created using any programming language or framework.

Terminology

There are some very specific terms used in relation to UIHooks, those being:

  • "Integration": This is an Integration created using the ZEIT Dashboard. This is the what a user installs
  • "Configuration": A single instance where a user added an Integration to an account (user or a team)
  • "UIHook": An Integration can render UI elements. These UI elements are requested from a HTTP endpoint defined in the Integration's implementation. The HTTP endpoint is called a UIHook

UIHooks are HTTP-Based

UIHooks are HTTP based, therefore they are not tied to any programming language or framework.

The process a UIHook uses is as follows:

  • The UIHook receives a HTTP POST request, including a JSON payload as the request body
  • The UIHook returns a HTML string, with components, as the response
  • A UIHook can be called via client side, so it needs to support CORS

The following example shows how you can implement a UIHook with Node.js and micro.

Note: This implementation is very similar to implementations in other languages.
const micro = require('micro')

let counter = 0

async function uiHook(req, res) {
  // Add CORS support. So UIHook can be called via client side.
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Authorization, Accept, Content-Type'
  )

  if (req.method === 'OPTIONS') {
    return micro.send(res, 200)
  }

  // UIHook works only with HTTP POST.
  if (req.method !== 'POST') {
    return micro.send(res, 404, '404 - Not Found')
  }

  // Extract payload from the POST body.
  const payload = await micro.json(req)
  if (payload.action === 'reset') {
    counter = 0
  } else {
    counter += 1
  }

  // Send HTML, with components, as the response
  return micro.send(
    res,
    200,
    `
    <Page>
      <P>Counter: ${counter}</P>
      <Button>Count Me</Button>
      <Button action="reset">Reset</Button>
    </Page>
  `
  )
}

// Start the HTTP server
const server = micro(uiHook)
const port = process.env.PORT || 5005

console.log(`UIHook started on http://localhost:${port}`)
server.listen(port, err => {
  if (err) {
    throw err
  }
})

For more examples of Integration implementations including the above example and others in more languages, such as PHP, Python, Go and Rust, see the Integration Gallery repository.

HTTP Payload

The payload used in the UIHook receives a JSON payload with different pieces of information regarding the related configuration of the Integration, those being:

  • action - The action of the UIHook (defaults to view)
  • clientState - An object containing the values of input components
  • slug - The slug of the Integration that sent the request
  • configurationId - The id of the configuration
  • integrationId - The id of the Integration
  • team - The team, if the Integration is added to a team
  • user - The user object of the user who is accessing the configuration
  • project - The project if the configuration is being accessing inside a project, otherwise null
  • token - A short-lived token which has authorization for the installed account
  • installationUrl - The URL of the current Integration configuration. (You can use this to redirect a user to another site using installationUrl as the redirect URL)
  • query - An object containing query parameters of the Integration installation page

OAuth Integrations

The main purpose of ZEIT Integrations is to introduce new capabilities inside the ZEIT Dashboard. This capability is provided by UIHooks.

However, you may need more capabilities beyond UIHooks. That is where OAuth support comes in handy.

OAuth Support

With our OAuth support, you can connect your app with ZEIT or provide "login with ZEIT" functionality.

Every ZEIT Integration is a full-fledged OAuth app. You can add the redirect URL when either creating or editing an Integration, this will provide you with OAuth credentials.

You can then follow our OAuth API reference to build an OAuth Integration.

Hybrid Mode

Sometimes, your Integration will use a UIHook but also need to access the ZEIT API in a background worker.

In this situation you cannot use the UIHook's token since it is only valid for the execution period of the UIHook.

That's where Hybrid Mode comes in handy. Hybrid Mode is a combination of both UIHooks and OAuth. This is how it works:

  • When you create your Integration, you can add both the Redirect URL and UI Hook URL
  • You will receive a public URL for your Integration in the following format:
https://zeit.co/integration/:slug
  • When the Integration installs, the OAuth authorization process is started
  • The user is then sent to the Redirect URL

The Redirect URL supports the following query parameters:

  • teamId - The teamId of the installation
  • configurationId - The id of the related configuration
  • code - OAuth authorization code (you can exchange an accessToken using this)
  • next - The installation URL of your Integration in the ZEIT Dashboard
Note: You can redirect the user to the next URL after obtaining the accessToken.

Now you can use UIHook's to display reports and receive inputs from the user. You can also use the obtained accessToken to access the ZEIT API from anywhere, at anytime.