# SimStudio AI Integration Pattern: Adding New Service Integrations to the Sim Monorepo

> Learn the SimStudio AI integration pattern for adding new service integrations. Implement Tools, Blocks, and register them in the sim monorepo apps/sim/tools and apps/sim/blocks.

- Repository: [Sim/sim](https://github.com/simstudioai/sim)
- Tags: how-to-guide
- Published: 2026-05-02

---

**Adding a new service integration to simstudioai/sim requires implementing a Tool in `apps/sim/tools/`, defining a Block in `apps/sim/blocks/`, registering both in their respective registry files, and wiring the UI components in the landing app catalog.**

The simstudioai/sim repository orchestrates workflows through a strict, layered architecture that separates API logic from UI representation. The integration pattern ensures type-safe communication between external services and the visual workflow editor by following a consistent four-step implementation path. This guide walks through the exact file structure, configuration requirements, and code patterns used to onboard new services, using the existing Zoom integration as a canonical reference.

## Step 1: Implement the Tool Layer

Tools are the low-level API clients that handle authentication, request validation, and external service communication. Each tool resides in its own directory under `apps/sim/tools/<service>/`.

### Tool Configuration Structure

Create `apps/sim/tools/<service>/index.ts` and export a `ToolConfig` object that defines the request/response schema and OAuth requirements:

```typescript
// apps/sim/tools/zoom/index.ts
import { ToolConfig } from '@/tools/types';
import { requestJson } from '@/lib/api/client/request';
import { zoomCreateMeetingContract } from '@/lib/api/contracts/zoom';
import { createLogger } from '@sim/logger';

const logger = createLogger('zoom-tool');

export const zoomCreateMeeting: ToolConfig<
  { topic: string; startTime: string }, 
  { joinUrl: string }
> = {
  id: 'zoom_create_meeting',
  name: 'Create Zoom Meeting',
  description: 'Creates a new Zoom meeting and returns the join URL.',
  version: '1.0.0',
  oauth: { required: true, provider: 'zoom' },
  request: async (params, context) => {
    logger.info('Creating Zoom meeting', { topic: params.topic });
    
    const data = await requestJson(zoomCreateMeetingContract, {
      body: params,
      headers: { Authorization: `Bearer ${context.accessToken}` },
    });
    
    return data;
  },
};

```

### OAuth and Request Handling

The `oauth` field declares whether the tool requires authentication and specifies the provider key. When `oauth.required` is true, the UI automatically surfaces a "Connect" button in the workflow editor. All HTTP requests must use the shared `requestJson` helper from `@/lib/api/client/request`, which validates requests against contracts defined in the `apps/sim/lib/api/contracts/` directory.

## Step 2: Register the Tool in the Barrel

Export the tool from the central barrel file to make it discoverable by the executor and block definitions:

```typescript
// apps/sim/tools/index.ts
export { zoomCreateMeeting } from './zoom';
export { googleCalendarCreateEvent } from './google_calendar';
// Additional tool exports...

```

The executor scans this registry at build time to populate the available tool inventory.

## Step 3: Create the Block Definition

Blocks are the visual nodes that users place on workflows. They delegate execution to Tools and define the UI inputs/outputs. Create `apps/sim/blocks/blocks/<service>.ts`:

```typescript
// apps/sim/blocks/blocks/zoom.ts
import { BlockConfig } from '@/blocks/types';
import { zoomCreateMeeting } from '@/tools/zoom';
import { ZoomIcon } from '@/components/emcn/icons/zoom';

export const ZoomBlock: BlockConfig = {
  type: 'zoom',
  name: 'Zoom',
  description: 'Create meetings, fetch recordings, etc.',
  category: 'tools',
  bgColor: '#0F9D58',
  icon: ZoomIcon,
  tools: {
    access: ['zoom_create_meeting'],
    config: {
      tool: (params) => `zoom_${params.operation}`,
      params: (params) => ({
        topic: params.topic,
        startTime: params.startTime,
      }),
    },
  },
  inputs: {
    operation: { type: 'select', options: ['create_meeting'] },
    topic: { type: 'short-input', required: true },
    startTime: { type: 'datetime-input', required: true },
  },
  outputs: {
    joinUrl: { type: 'short-output' },
  },
};

```

### Linking Tools to Blocks

The `tools.access` array lists which tool IDs the block can invoke. The `tools.config.tool` function maps block parameters to tool IDs, while `tools.config.params` transforms resolved variables into the exact shape required by the tool's request function.

### Critical Implementation Rules

When configuring the `tools` object, follow these repository conventions to avoid runtime errors:

- **Never perform type coercion in `tools.config.tool`** – this runs before variable resolution and cannot access runtime values.
- **Perform all transformations in `tools.config.params`** – this executes after variables are resolved and has access to the fully hydrated parameter object.
- **Generate resource IDs using `generateId()` or `generateShortId()`** from `@sim/utils/id` when creating resources in external services.
- **Keep Zod schemas out of block files** – place all validation logic in API contracts under `apps/sim/lib/api/contracts/`.

## Step 4: Register the Block

Export the block from the registry to activate it in the workflow editor:

```typescript
// apps/sim/blocks/registry.ts
export { ZoomBlock } from './blocks/zoom';
export { GoogleCalendarBlock } from './blocks/google_calendar';
// Additional block exports...

```

## Step 5: Define API Contracts

All HTTP boundaries live in `apps/sim/lib/api/contracts/<service>.ts`. Contracts provide Zod schemas for request/response validation that are shared between the client and server:

```typescript
// apps/sim/lib/api/contracts/zoom.ts
import { defineRouteContract } from '@/lib/api/contracts';
import { z } from 'zod';

export const createZoomMeetingBodySchema = z.object({
  topic: z.string().min(1),
  startTime: z.string().datetime(),
});

export const createZoomMeetingResponseSchema = z.object({
  joinUrl: z.string().url(),
});

export const zoomCreateMeetingContract = defineRouteContract({
  method: 'POST',
  path: '/api/tools/zoom/createMeeting',
  body: createZoomMeetingBodySchema,
  response: { mode: 'json', schema: createZoomMeetingResponseSchema },
});

```

This pattern guarantees end-to-end type safety without duplicating validation logic.

## Step 6: Configure the UI Catalog

The Integrations landing page discovers available services through a JSON registry and renders cards using dedicated components.

Add an entry to the catalog data file:

```json
// apps/sim/app/(landing)/integrations/data/integrations.json
{
  "id": "zoom",
  "name": "Zoom",
  "icon": "zoom",
  "description": "Schedule and manage Zoom meetings directly from Sim.",
  "category": "Video Conferencing"
}

```

Create the icon component at [`apps/sim/components/emcn/icons/zoom.tsx`](https://github.com/simstudioai/sim/blob/main/apps/sim/components/emcn/icons/zoom.tsx) and ensure it matches the `icon` value in the JSON entry. The [`integration-card.tsx`](https://github.com/simstudioai/sim/blob/main/integration-card.tsx) component in `apps/sim/app/(landing)/integrations/components/` automatically renders the card using this data.

## Step 7: Implement Optional Triggers

For services that support webhooks or event streaming, create a trigger implementation in `apps/sim/triggers/<service>/`:

```typescript
// apps/sim/triggers/zoom/webhook.ts
import { generateId } from '@sim/utils/id';

export const handleZoomWebhook = async (payload: unknown) => {
  const eventId = generateId();
  // Process webhook payload and enqueue workflow execution
};

```

Register the trigger in [`apps/sim/triggers/registry.ts`](https://github.com/simstudioai/sim/blob/main/apps/sim/triggers/registry.ts) using the same barrel export pattern used for tools and blocks.

## Step 8: Testing and Validation

Every integration must include comprehensive test coverage and pass repository-specific CI checks.

Place unit tests adjacent to the implementation files:

```typescript
// apps/sim/tools/zoom/index.test.ts
import { describe, it, expect } from 'vitest';
import { zoomCreateMeeting } from './index';

describe('zoomCreateMeeting', () => {
  it('should validate required parameters', async () => {
    // Test implementation using global mocks from @sim/testing
  });
});

```

Before committing, run the validation scripts to ensure compliance with repository boundaries:

```bash
bun run check:api-validation    # Verifies no route-local Zod usage

bun run lint                      # ESLint and Prettier checks

bun test                          # Vitest suite execution

```

The `check:api-validation` script enforces that all Zod validation occurs within contract files, not in route handlers or tool implementations, maintaining the architectural boundary between API layers.

## Summary

- **Implement Tools** in `apps/sim/tools/<service>/index.ts` using the `ToolConfig` type, handling OAuth via the `oauth` field and API calls through `requestJson`.
- **Register Tools** by exporting them from [`apps/sim/tools/index.ts`](https://github.com/simstudioai/sim/blob/main/apps/sim/tools/index.ts) to make them available to the executor and block definitions.
- **Define Blocks** in `apps/sim/blocks/blocks/<service>.ts` using `BlockConfig`, linking to tools via the `tools.access` array and mapping inputs via `tools.config.params`.
- **Create Contracts** in `apps/sim/lib/api/contracts/<service>.ts` using `defineRouteContract` to share Zod schemas between client and server.
- **Wire the UI** by adding entries to `apps/sim/app/(landing)/integrations/data/integrations.json` and creating icon components in `apps/sim/components/emcn/icons/`.
- **Respect Boundaries** by keeping Zod schemas in contracts, using `generateId()` for resource creation, and running `check:api-validation` before submitting pull requests.

## Frequently Asked Questions

### What is the difference between a Tool and a Block in Sim?

A **Tool** is the low-level API client that handles HTTP requests, OAuth tokens, and external service communication, located in `apps/sim/tools/`. A **Block** is the visual workflow node that users interact with in the editor, located in `apps/sim/blocks/`, which delegates execution to Tools and defines the UI inputs and outputs. Blocks provide the user interface; Tools provide the implementation.

### Where should Zod validation schemas be defined?

All Zod schemas must be defined in `apps/sim/lib/api/contracts/<service>.ts` using the `defineRouteContract` helper. The repository enforces this via the `check:api-validation` script, which fails if Zod schemas are defined directly in route handlers, tool implementations, or block configurations. This centralization ensures type safety across the client-server boundary.

### How does OAuth configuration work in the Sim integration pattern?

OAuth is declared in the Tool's `ToolConfig` via the `oauth` field, specifying `required: true` and the provider identifier. The UI layer automatically detects this configuration and renders the appropriate "Connect" button in the integration catalog. During execution, the Tool receives the access token through the `context` parameter, which the Tool uses to authenticate requests to the external service.

### What testing requirements exist for new integrations?

Every new Tool and Block requires unit tests placed in a `*.test.ts` file adjacent to the implementation. Tests must use the global mocks available from `@sim/testing` and verify both the Zod validation logic and the runtime behavior of the Tool. Additionally, all integrations must pass `bun run check:api-validation`, `bun run lint`, and the full `bun test` suite before merging.