> ## Documentation Index
> Fetch the complete documentation index at: https://docs.membrane.agency/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Connection UI

You can open the prebuilt connection UI for a specific integration with:

```javascript theme={null}
await membrane.ui.connect({ integrationKey: 'hubspot' })
```

<br />

This guide shows you how you can implement a custom connection UI

## 1. Get Auth Options

To get information about what to ask the user in order to create a connection, fetch connection options:

```javascript theme={null}
const integration = await membrane.integration('hubspot').get()

// Fetch connection options
const connectionOptions = await membrane.post('/connection-options', {
  integrationId: integration.id,
})
```

<br />

For simplicity, we'll assume there is only one auth option. If there are more, you can use the same logic for each of them.
If there are zero options, it means authentication is not configured and you need to edit or change the connector.

Each connection option contains:

* `key` – Unique identifier for this auth option (pass to `authOptionKey` when connecting)
* `type` – The authentication type (e.g., 'oauth2', 'client-credentials')
* `title` – Human-readable name for this auth option
* `description` – Description explaining this auth method
* `inputSchema` – [Data Schema](/docs/references/data-schemas) of the parameters required to create a connection. It will be empty if parameters are not required.

## 2. Connection Parameters UI

You can use your preferred way of converting [JSON Schema](https://json-schema.org/) into a form.
For example, you can extract a list of fields from the schema and simply display an input for each:

```javascript theme={null}
function ConnectionForm() {
  const [input, setInput] = useState({})

  const schema = {
    type: 'object',
    properties: {
      email: { type: 'string' },
      apiKey: { type: 'string' },
    },
  }

  const fields = Object.keys(schema.properties)

  return (
    <>
      {fields.map((field) => (
        <div key={field}>
          <label>{field}</label>
          <input
            type='text'
            className='p-1 border rounded'
            value={input[field] || ''}
            onChange={(e) => setInput({ ...input, [field]: e.target.value })}
          />
        </div>
      ))}
    </>
  )
}
```

<br />

If a connection doesn't have parameters, you can skip this step.

## 3. Creating a Connection

When you have collected connection input, you can create a connection:

```javascript theme={null}
const connection = await membrane.integration('hubspot').connect({ input })
```

<br />

> This code may open a new window if it is required by the authentication process.
> Make sure this code runs inside a user action handler (e.g. `onClick`), otherwise the browser may block the new window.

<br />

**Redirect instead of a new window**

When you need to avoid opening a new window, you can use redirect instead.
Pass `sameWindow: true` and `redirectUri` options. The URI will be used for redirection after the connection is created or fails.

Added query parameters:

* For successful creation, the `connectionId` will be added
* For failures, `error` will contain the error message and `errorData` will contain the JSON stringified error payload

```javascript theme={null}
const connection = await membrane.integration('hubspot').connect({
  input,
  sameWindow: true,
  redirectUri: window.location.href,
})
```

<br />

## 4. Putting it all together

Here is a simple UI component that displays the connection parameters and creates a new connection:

```javascript theme={null}
import { useState, useEffect } from 'react'
import { useIntegration, useMembrane } from '@membranehq/react'

function Component({ integrationKey }) {
  // For this to work, wrap this component in <MembraneProvider/>
  const membrane = useMembrane()

  const { integration, error } = useIntegration(integrationKey)

  const [connectionOptions, setConnectionOptions] = useState(null)
  const [connectionInput, setConnectionInput] = useState({})
  const [connecting, setConnecting] = useState(false)
  const [connection, setConnection] = useState(null)

  // Fetch connection options
  useEffect(() => {
    if (!integration?.id) return

    membrane
      .post('/connection-options', {
        integrationId: integration.id,
      })
      .then(setConnectionOptions)
  }, [integration?.id, membrane])

  // If something bad happened - show the error
  if (error) {
    return <p>Error: {error.message}</p>
  }

  // Wait until the spec and connection options load
  if (!integration || !connectionOptions) {
    return <p>Loading...</p>
  }

  // Display only the first option
  const option = connectionOptions[0]

  if (!option) {
    return <p>No auth options found for this integration</p>
  }

  // Get connection input schema
  const schema = option.inputSchema

  // Simplified way to get the list of connection parameters.
  const fields = schema ? Object.keys(schema.properties ?? {}) : []

  async function connect() {
    setConnecting(true)
    try {
      const connection = await membrane.integration(integrationKey).connect({
        input: connectionInput,
        authOptionKey: option.key,
      })
      setConnection(connection)
    } finally {
      setConnecting(false)
    }
  }

  return (
    <div>
      {fields.length > 0 && (
        <div>
          <strong>Connection Parameters:</strong>
          {fields.map((field) => (
            <div key={field}>
              <label>{field}</label>
              <input
                type='text'
                value={connectionInput[field] || ''}
                onChange={(e) =>
                  setConnectionInput({
                    ...connectionInput,
                    [field]: e.target.value,
                  })
                }
              />
            </div>
          ))}
        </div>
      )}

      {connecting ? <span>Connecting...</span> : <button onClick={connect}>Connect</button>}

      {connection && (
        <div>
          <strong>Connected!</strong>
          <br />
          Connection Id: {connection.id}
        </div>
      )}
    </div>
  )
}
```

<br />

## 5. Re-connecting

When a connection becomes disconnected, you can re-connect it using the same code as when creating a new connection:

```javascript theme={null}
const connection = await membrane.integration('hubspot').connect({ input })
```

<br />

## Creating or updating connections

Every completed `connect(...)` flow creates a new connection when you target an
integration (`integrationId` / `integrationKey`), connector
(`connectorId` / `connectorKey`), or external app
(`externalAppId` / `externalAppKey`).

To re-authenticate or update an existing connection in place, pass its
`connectionId`. That is the only path that writes fresh credentials to an
existing connection record.
