# Build an integration (/docs/build/integrations)



An integration is an npm package that plugs your service into Appbrew apps. You develop it inside your app repo as a local workspace package, and it ships to merchants as a normal dependency.

## Scaffold [#scaffold]

From your app repo root:

```bash
pnpm create-integration my-integration
pnpm install
```

This creates the package and links it into the workspace:

<Files>
  <Folder name="packages/my-integration">
    <File name="package.json" />

    <Folder name="src">
      <File name="index.ts" />
    </Folder>
  </Folder>
</Files>

Because the app consumes it as a `workspace:*` dependency, every edit under `packages/my-integration/src/` hot-reloads in the running app, with no build or publish step during development. Import it by package name, exactly as consumers will after it is published:

```ts
import { MyBlock } from 'my-integration'
```

## Package requirements [#package-requirements]

Appbrew apps compile integration packages from TypeScript source at build time, so the package must ship source, not a bundle:

* `main` and `types` point at `src/index.ts`
* `files` lists `["src/"]`
* Every `@gauntlet/*` package you import is a **peer dependency** — the host app provides it; never bundle it. Same for native modules your integration needs (e.g. `react-native-webview`).
* Export everything through `src/index.ts`; it is the package's only entry point.

The scaffold generates a `package.json` that already satisfies this shape (including the baseline peer dependency and publish metadata); keep it intact as you add your own peer dependencies.

## What an integration can ship [#what-an-integration-can-ship]

Three things, and most packages combine at least two:

1. **Blocks** — UI components merchants place on screens, exported together with a `register{Name}Blocks` function.
2. **An analytics tracker** — a class that forwards app events to your platform. See [Analytics trackers](/docs/build/analytics-trackers).
3. **A manifest** — an `appbrew` key in `package.json` that tells the platform what your integration is and what merchants configure.

### Blocks and the register function [#blocks-and-the-register-function]

Blocks inside a package are written exactly like [custom blocks](/docs/build/custom-blocks); only the registration differs. Export one function that registers all of them:

```ts title="packages/my-integration/src/register.ts"
import { ReviewsCarousel } from './blocks/reviews-carousel'
import { ReviewsBadge } from './blocks/reviews-badge'

export function registerMyIntegrationBlocks(r: Map<string, any>) {
  r.set('my-integration-reviews-carousel', ReviewsCarousel)
  r.set('my-integration-reviews-badge', ReviewsBadge)
}
```

Prefix block ids with your integration name. Ids share one registry with every other package the app installs.

### The manifest [#the-manifest]

The `appbrew` key declares the merchant-facing settings, the key your code reads config under, and whether installing the integration requires a native rebuild (true whenever you add native modules; false for pure-JS packages):

```json title="packages/my-integration/package.json"
{
  "name": "my-integration",
  "version": "1.0.0",
  "main": "src/index.ts",
  "types": "src/index.ts",
  "files": ["src/"],
  "peerDependencies": {
    "@gauntlet/analytics": "*",
    "@gauntlet/state": "*",
    "@gauntlet/types": "*",
    "@gauntlet/ui-builder": "*"
  },
  "appbrew": {
    "configKey": "my-integration",
    "requiresNativeBuild": false,
    "settings": [
      { "key": "apiKey", "title": "API key" },
      { "key": "merchantId", "title": "Merchant ID", "placeholder": "Enter Merchant ID" },
      { "key": "enableBadges", "title": "Show review badges", "type": "boolean" }
    ]
  }
}
```

What merchants enter under `settings` lands in the app config under your `configKey`, at `integrations["my-integration"]`, where your blocks and tracker read it. The full field reference lives in the [manifest schema](/docs/reference/manifest-schema).

## Wire it into the app [#wire-it-into-the-app]

Register your blocks in the app's registration file. Packages keep exporting `register{Name}Blocks` so hosts on any platform version can consume them; on 0.27.0 and later the host can wire the same blocks through `app.components.register` on the [`app` extension host](/docs/build/extending-app-behavior) from `@gauntlet/brewery`.

<Tabs items="[&#x22;app object (0.27+)&#x22;, &#x22;blockRegistry (all versions)&#x22;]">
  <Tab value="app object (0.27+)">
    ```ts title="src/app/register-blocks.ts"
    import { app } from '@gauntlet/brewery'
    import { ReviewsBadge, ReviewsCarousel } from 'my-integration'

    app.components.register('block', 'my-integration-reviews-carousel', ReviewsCarousel)
    app.components.register('block', 'my-integration-reviews-badge', ReviewsBadge)
    ```

    Returns an undo, and the same call registers screens, icons, and product
    cards — see [Extending app behavior](/docs/build/extending-app-behavior).
  </Tab>

  <Tab value="blockRegistry (all versions)">
    ```ts title="src/app/register-blocks.ts"
    import { blockRegistry, registerCommonBlocks } from '@gauntlet/block-registry'
    import { registerMyIntegrationBlocks } from 'my-integration'

    export function registerBlocks() {
      const r = blockRegistry
      registerCommonBlocks(r)
      registerMyIntegrationBlocks(r)
    }
    ```

    This is the form the app scaffold generates today. It works on every
    platform version, 0.27 included. Your register function runs after the
    common blocks.
  </Tab>
</Tabs>

Trackers are wired in `src/app/App.tsx`; [Analytics trackers](/docs/build/analytics-trackers) covers that half.

## Next [#next]

* Add a tracker: [Analytics trackers](/docs/build/analytics-trackers)
* Verify everything before handoff: [Testing your work](/docs/build/testing)
* Publish the package: [Publishing packages](/docs/ship/publishing-packages)
