sim

Sim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000+ builders.

29,416 stars TypeScript Markdown Skills API Spec #agent-workflow#agentic-workflow#agents#ai
AI Prompts & Specs

Repository: simstudioai/sim


Stars: 27814

CLAUDE.md

Sim Development Guidelines

You are a professional software engineer. All code must follow best practices: accurate, readable, clean, and efficient.

Global Standards

- Logging: Import createLogger from @sim/logger. Use logger.info, logger.warn, logger.error instead of console.log
- Comments: Use TSDoc for documentation. No ==== separators. No non-TSDoc comments
- Styling: Never update global styles. Keep all styling local to components
- ID Generation: Never use crypto.randomUUID(), nanoid, or uuid package. Use generateId() (UUID v4) or generateShortId() (compact) from @/lib/core/utils/uuid
- Package Manager: Use bun and bunx, not npm and npx

Architecture

Core Principles


1. Single Responsibility: Each component, hook, store has one clear purpose
2. Composition Over Complexity: Break down complex logic into smaller pieces
3. Type Safety First: TypeScript interfaces for all props, state, return types
4. Predictable State: Zustand for global state, useState for UI-only concerns

Root Structure


text
apps/sim/
β”œβ”€β”€ app/ # Next.js app router (pages, API routes)
β”œβ”€β”€ blocks/ # Block definitions and registry
β”œβ”€β”€ components/ # Shared UI (emcn/, ui/)
β”œβ”€β”€ executor/ # Workflow execution engine
β”œβ”€β”€ hooks/ # Shared hooks (queries/, selectors/)
β”œβ”€β”€ lib/ # App-wide utilities
β”œβ”€β”€ providers/ # LLM provider integrations
β”œβ”€β”€ stores/ # Zustand stores
β”œβ”€β”€ tools/ # Tool definitions
└── triggers/ # Trigger definitions

Naming Conventions


- Components: PascalCase (WorkflowList)
- Hooks: use prefix (useWorkflowOperations)
- Files: kebab-case (workflow-list.tsx)
- Stores: stores/feature/store.ts
- Constants: SCREAMING_SNAKE_CASE
- Interfaces: PascalCase with suffix (WorkflowListProps)

Imports

Always use absolute imports. Never use relative imports.

typescript
// βœ“ Good
import { useWorkflowStore } from '@/stores/workflows/store'

// βœ— Bad
import { useWorkflowStore } from '../../../stores/workflows/store'

Use barrel exports (index.ts) when a folder has 3+ exports. Do not re-export from non-barrel files; import directly from the source.

Import Order


1. React/core libraries
2. External libraries
3. UI components (@/components/emcn, @/components/ui)
4. Utilities (@/lib/...)
5. Stores (@/stores/...)
6. Feature imports
7. CSS imports

Use import type { X } for type-only imports.

TypeScript

1. No any - Use proper types or unknown with type guards
2. Always define props interface for components
3. as const for constant objects/arrays
4. Explicit ref types: useRef<HTMLDivElement>(null)

Components

typescript
'use client' // Only if using hooks

const CONFIG = { SPACING: 8 } as const

interface ComponentProps {
requiredProp: string
optionalProp?: boolean
}

export function Component({ requiredProp, optionalProp = false }: ComponentProps) {
// Order: refs β†’ external hooks β†’ store hooks β†’ custom hooks β†’ state β†’ useMemo β†’ useCallback β†’ useEffect β†’ return
}

Extract when: 50+ lines, used in 2+ files, or has own state/logic. Keep inline when: < 10 lines, single use, purely presentational.

Hooks

typescript
interface UseFeatureProps { id: string }

export function useFeature({ id }: UseFeatureProps) {
const idRef = useRef(id)
const [data, setData] = useState<Data | null>(null)

useEffect(() => { idRef.current = id }, [id])

const fetchData = useCallback(async () => { ... }, []) // Empty deps when using refs

return { data, fetchData }
}

Zustand Stores

Stores live in stores/. Complex stores split into store.ts + types.ts.

typescript
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

const initialState = { items: [] as Item[] }

export const useFeatureStore = create<FeatureState>()(
devtools(
(set, get) => ({
...initialState,
setItems: (items) => set({ items }),
reset: () => set(initialState),
}),
{ name: 'feature-store' }
)
)

Use devtools middleware. Use persist only when data should survive reload with partialize to persist only necessary state.

React Query

All React Query hooks live in hooks/queries/. All server state must go through React Query β€” never use useState + fetch in components for data fetching or mutations.

Query Key Factory

Every file must have a hierarchical key factory with an all root key and intermediate plural keys for prefix invalidation:

typescript
export const entityKeys = {
all: ['entity'] as const,
lists: () => [...entityKeys.all, 'list'] as const,
list: (workspaceId?: string) => [...entityKeys.lists(), workspaceId ?? ''] as const,
details: () => [...entityKeys.all, 'detail'] as const,
detail: (id?: string) => [...entityKeys.details(), id ?? ''] as const,
}

Query Hooks

- Every queryFn must forward signal for request cancellation
- Every query must have an explicit staleTime
- Use keepPreviousData only on variable-key queries (where params change), never on static keys

typescript
export function useEntityList(workspaceId?: string) {
return useQuery({
queryKey: entityKeys.list(workspaceId),
queryFn: ({ signal }) => fetchEntities(workspaceId as string, signal),
enabled: Boolean(workspaceId),
staleTime: 60 * 1000,
placeholderData: keepPreviousData, // OK: workspaceId varies
})
}

Mutation Hooks

- Use targeted invalidation (entityKeys.lists()) not broad (entityKeys.all) when possible
- For optimistic updates: use onSettled (not onSuccess) for cache reconciliation β€” onSettled fires on both success and error
- Don't include mutation objects in useCallback deps β€” .mutate() is stable in TanStack Query v5

typescript
export function useUpdateEntity() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (variables) => { / ... / },
onMutate: async (variables) => {
await queryClient.cancelQueries({ queryKey: entityKeys.detail(variables.id) })
const previous = queryClient.getQueryData(entityKeys.detail(variables.id))
queryClient.setQueryData(entityKeys.detail(variables.id), / optimistic /)
return { previous }
},
onError: (_err, variables, context) => {
queryClient.setQueryData(entityKeys.detail(variables.id), context?.previous)
},
onSettled: (_data, _error, variables) => {
queryClient.invalidateQueries({ queryKey: entityKeys.lists() })
queryClient.invalidateQueries({ queryKey: entityKeys.detail(variables.id) })
},
})
}

Styling

Use Tailwind only, no inline styles. Use cn() from @/lib/utils for conditional classes.

typescript
<div className={cn('base-classes', isActive && 'active-classes')} />

EMCN Components

Import from @/components/emcn, never from subpaths (except CSS files). Use CVA when 2+ variants exist.

Testing

Use Vitest. Test files: feature.ts β†’ feature.test.ts. See .cursor/rules/sim-testing.mdc for full details.

Global Mocks (vitest.setup.ts)

@sim/db, drizzle-orm, @sim/logger, @/blocks/registry, @trigger.dev/sdk, and store mocks are provided globally. Do NOT re-mock them unless overriding behavior.

Standard Test Pattern

typescript
/
* @vitest-environment node
*/
import { createMockRequest } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'

const { mockGetSession } = vi.hoisted(() => ({
mockGetSession: vi.fn(),
}))

vi.mock('@/lib/auth', () => ({
auth: { api: { getSession: vi.fn() } },
getSession: mockGetSession,
}))

import { GET } from '@/app/api/my-route/route'

describe('my route', () => {
beforeEach(() => {
vi.clearAllMocks()
mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
})
it('returns data', async () => { ... })
})

Performance Rules

- NEVER use vi.resetModules() + vi.doMock() + await import() β€” use vi.hoisted() + vi.mock() + static imports
- NEVER use vi.importActual() β€” mock everything explicitly
- NEVER use mockAuth(), mockConsoleLogger(), setupCommonApiMocks() from @sim/testing β€” they use vi.doMock() internally
- Mock heavy deps (@/blocks, @/tools/registry, @/triggers) in tests that don't need them
- Use @vitest-environment node unless DOM APIs are needed (window, document, FormData)
- Avoid real timers β€” use 1ms delays or vi.useFakeTimers()

Use @sim/testing mocks/factories over local test data.

Utils Rules

- Never create utils.ts for single consumer - inline it
- Create utils.ts when 2+ files need the same helper
- Check existing sources in lib/ before duplicating

Adding Integrations

New integrations require: Tools β†’ Block β†’ Icon β†’ (optional) Trigger

Always look up the service's API docs first.

1. Tools (tools/{service}/)

text
tools/{service}/
β”œβ”€β”€ index.ts # Barrel export
β”œβ”€β”€ types.ts # Params/response types
└── {action}.ts # Tool implementation

Tool structure:

typescript
export const serviceTool: ToolConfig<Params, Response> = {
id: 'service_action',
name: 'Service Action',
description: '...',
version: '1.0.0',
oauth: { required: true, provider: 'service' },
params: { / ... / },
request: { url: '/api/tools/service/action', method: 'POST', ... },
transformResponse: async (response) => { / ... / },
outputs: { / ... / },
}

Register in tools/registry.ts.

2. Block (blocks/blocks/{service}.ts)

typescript
export const ServiceBlock: BlockConfig = {
type: 'service',
name: 'Service',
description: '...',
category: 'tools',
bgColor: '#hexcolor',
icon: ServiceIcon,
subBlocks: [ / see SubBlock Properties / ],
tools: { access: ['service_action'], config: { tool: (p) => service_${p.operation}, params: (p) => ({ / type coercions here / }) } },
inputs: { / ... / },
outputs: { / ... / },
}

Register in blocks/registry.ts (alphabetically).

Important: tools.config.tool runs during serialization (before variable resolution). Never do Number() or other type coercions there β€” dynamic references like <Block.output> will be destroyed. Use tools.config.params for type coercions (it runs during execution, after variables are resolved).

SubBlock Properties:

typescript
{
id: 'field', title: 'Label', type: 'short-input', placeholder: '...',
required: true, // or condition object
condition: { field: 'op', value: 'send' }, // show/hide
dependsOn: ['credential'], // clear when dep changes
mode: 'basic', // 'basic' | 'advanced' | 'both' | 'trigger'
}

condition examples:
- { field: 'op', value: 'send' } - show when op === 'send'
- { field: 'op', value: ['a','b'] } - show when op is 'a' OR 'b'
- { field: 'op', value: 'x', not: true } - show when op !== 'x'
- { field: 'op', value: 'x', not: true, and: { field: 'type', value: 'dm', not: true } } - complex

dependsOn: ['field'] or { all: ['a'], any: ['b', 'c'] }

File Input Pattern (basic/advanced mode):

typescript
// Basic: file-upload UI
{ id: 'uploadFile', type: 'file-upload', canonicalParamId: 'file', mode: 'basic' },
// Advanced: reference from other blocks
{ id: 'fileRef', type: 'short-input', canonicalParamId: 'file', mode: 'advanced' },

In tools.config.tool, normalize with:

typescript
import { normalizeFileInput } from '@/blocks/utils'
const file = normalizeFileInput(params.uploadFile || params.fileRef, { single: true })
if (file) params.file = file

For file uploads, create an internal API route (/api/tools/{service}/upload) that uses downloadFileFromStorage to get file content from UserFile objects.

3. Icon (components/icons.tsx)

typescript
export function ServiceIcon(props: SVGProps<SVGSVGElement>) {
return <svg {...props}>/ SVG from brand assets /</svg>
}

4. Trigger (triggers/{service}/) - Optional

text
triggers/{service}/
β”œβ”€β”€ index.ts # Barrel export
β”œβ”€β”€ webhook.ts # Webhook handler
└── {event}.ts # Event-specific handlers

Register in triggers/registry.ts.

Integration Checklist

- [ ] Look up API docs
- [ ] Create tools/{service}/ with types and tools
- [ ] Register tools in tools/registry.ts
- [ ] Add icon to components/icons.tsx
- [ ] Create block in blocks/blocks/{service}.ts
- [ ] Register block in blocks/registry.ts
- [ ] (Optional) Create and register triggers
- [ ] (If file uploads) Create internal API route with downloadFileFromStorage
- [ ] (If file uploads) Use normalizeFileInput in block config


README.md

<p align="center">
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="apps/sim/public/logo/wordmark.svg">
<source media="(prefers-color-scheme: light)" srcset="apps/sim/public/logo/wordmark-dark.svg">
<img src="apps/sim/public/logo/wordmark-dark.svg" alt="Sim Logo" width="380"/>
</picture>
</a>
</p>

<p align="center">The open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to orchestrate agentic workflows.</p>

<p align="center">
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/sim.ai-33c482" alt="Sim.ai"></a>
<a href="https://discord.gg/Hr4UWYEcTT" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
<a href="https://x.com/simdotai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/twitter/follow/simdotai?style=social" alt="Twitter"></a>
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-33c482.svg" alt="Documentation"></a>
</p>

<p align="center">
<a href="https://deepwiki.com/simstudioai/sim" target="_blank" rel="noopener noreferrer"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a> <a href="https://cursor.com/link/prompt?text=Help%20me%20set%20up%20Sim%20locally.%20Follow%20these%20steps%3A%0A%0A1.%20First%2C%20verify%20Docker%20is%20installed%20and%20running%3A%0A%20%20%20docker%20--version%0A%20%20%20docker%20info%0A%0A2.%20Clone%20the%20repository%3A%0A%20%20%20git%20clone%20https%3A%2F%2Fgithub.com%2Fsimstudioai%2Fsim.git%0A%20%20%20cd%20sim%0A%0A3.%20Start%20the%20services%20with%20Docker%20Compose%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20up%20-d%0A%0A4.%20Wait%20for%20all%20containers%20to%20be%20healthy%20(this%20may%20take%201-2%20minutes)%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20ps%0A%0A5.%20Verify%20the%20app%20is%20accessible%20at%20http%3A%2F%2Flocalhost%3A3000%0A%0AIf%20there%20are%20any%20errors%2C%20help%20me%20troubleshoot%20them.%20Common%20issues%3A%0A-%20Port%203000%2C%203002%2C%20or%205432%20already%20in%20use%0A-%20Docker%20not%20running%0A-%20Insufficient%20memory%20(needs%2012GB%2B%20RAM)%0A%0AFor%20local%20AI%20models%20with%20Ollama%2C%20use%20this%20instead%20of%20step%203%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.ollama.yml%20--profile%20setup%20up%20-d"><img src="https://img.shields.io/badge/Set%20Up%20with-Cursor-000000?logo=cursor&logoColor=white" alt="Set Up with Cursor"></a>
</p>

Build Workflows with Ease


Design agent workflows visually on a canvasβ€”connect agents, tools, and blocks, then run them instantly.

<p align="center">
<img src="apps/sim/public/static/workflow.gif" alt="Workflow Builder Demo" width="800"/>
</p>

Supercharge with Copilot


Leverage Copilot to generate nodes, fix errors, and iterate on flows directly from natural language.

<p align="center">
<img src="apps/sim/public/static/copilot.gif" alt="Copilot Demo" width="800"/>
</p>

Integrate Vector Databases


Upload documents to a vector store and let agents answer questions grounded in your specific content.

<p align="center">
<img src="apps/sim/public/static/knowledge.gif" alt="Knowledge Uploads and Retrieval Demo" width="800"/>
</p>

Quickstart

Cloud-hosted: sim.ai

<a href="https://sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/sim.ai-33c482?logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iNjE2IiBoZWlnaHQ9IjYxNiIgdmlld0JveD0iMCAwIDYxNiA2MTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTU5XzMxMykiPgo8cGF0aCBkPSJNNjE2IDBIMFY2MTZINjE2VjBaIiBmaWxsPSIjMzNjNDgyIi8+CjxwYXRoIGQ9Ik04MyAzNjUuNTY3SDExM0MxMTMgMzczLjgwNSAxMTYgMzgwLjM3MyAxMjIgMzg1LjI3MkMxMjggMzg5Ljk0OCAxMzYuMTExIDM5Mi4yODUgMTQ2LjMzMyAzOTIuMjg1QzE1Ny40NDQgMzkyLjI4NSAxNjYgMzkwLjE3MSAxNzIgMzg1LjkzOUMxNzcuOTk5IDM4MS40ODcgMTgxIDM3NS41ODYgMTgxIDM2OC4yMzlDMTgxIDM2Mi44OTUgMTc5LjMzMyAzNTguNDQyIDE3NiAzNTQuODhDMTcyLjg4OSAzNTEuMzE4IDE2Ny4xMTEgMzQ4LjQyMiAxNTguNjY3IDM0Ni4xOTZMMTMwIDMzOS41MTdDMTE1LjU1NSAzMzUuOTU1IDEwNC43NzggMzMwLjQ5OSA5Ny42NjY1IDMyMy4xNTFDOTAuNzc3NSAzMTUuODA0IDg3LjMzMzQgMzA2LjExOSA4Ny4zMzM0IDI5NC4wOTZDODcuMzMzNCAyODQuMDc2IDg5Ljg4OSAyNzUuMzkyIDk0Ljk5OTYgMjY4LjA0NUMxMDAuMzMzIDI2MC42OTcgMTA3LjU1NSAyNTUuMDIgMTE2LjY2NiAyNTEuMDEyQzEyNiAyNDcuMDA0IDEzNi42NjcgMjQ1IDE0OC42NjYgMjQ1QzE2MC42NjcgMjQ1IDE3MSAyNDcuMTE2IDE3OS42NjcgMjUxLjM0NkMxODguNTU1IDI1NS41NzYgMTk1LjQ0NCAyNjEuNDc3IDIwMC4zMzMgMjY5LjA0N0MyMDUuNDQ0IDI3Ni42MTcgMjA4LjExMSAyODUuNjM0IDIwOC4zMzMgMjk2LjA5OUgxNzguMzMzQzE3OC4xMTEgMjg3LjYzOCAxNzUuMzMzIDI4MS4wNyAxNjkuOTk5IDI3Ni4zOTRDMTY0LjY2NiAyNzEuNzE5IDE1Ny4yMjIgMjY5LjM4MSAxNDcuNjY3IDI2OS4zODFDMTM3Ljg4OSAyNjkuMzgxIDEzMC4zMzMgMjcxLjQ5NiAxMjUgMjc1LjcyNkMxMTkuNjY2IDI3OS45NTcgMTE3IDI4NS43NDYgMTE3IDI5My4wOTNDMTE3IDMwNC4wMDMgMTI1IDMxMS40NjIgMTQxIDMxNS40N0wxNjkuNjY3IDMyMi40ODNDMTgzLjQ0NSAzMjUuNiAxOTMuNzc4IDMzMC43MjIgMjAwLjY2NyAzMzcuODQ3QzIwNy41NTUgMzQ0Ljc0OSAyMTEgMzU0LjIxMiAyMTEgMzY2LjIzNUMyMTEgMzc2LjQ3NyAyMDguMjIyIDM4NS40OTQgMjAyLjY2NiAzOTMuMjg3QzE5Ny4xMTEgNDAwLjg1NyAxODkuNDQ0IDQwNi43NTggMTc5LjY2NyA0MTAuOTg5QzE3MC4xMTEgNDE0Ljk5NiAxNTguNzc4IDQxNyAxNDUuNjY3IDQxN0MxMjYuNTU1IDQxNyAxMTEuMzMzIDQxMi4zMjUgOTkuOTk5NyA0MDIuOTczQzg4LjY2NjggMzkzLjYyMSA4MyAzODEuMTUzIDgzIDM2NS41NjdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjMyLjI5MSA0MTNWMjUwLjA4MkMyNDQuNjg0IDI1NC42MTQgMjUwLjE0OCAyNTQuNjE0IDI2My4zNzEgMjUwLjA4MlY0MTNIMjMyLjI5MVpNMjQ3LjUgMjM5LjMxM0MyNDEuOTkgMjM5LjMxMyAyMzcuMTQgMjM3LjMxMyAyMzIuOTUyIDIzMy4zMTZDMjI4Ljk4NCAyMjkuMDk1IDIyNyAyMjQuMjA5IDIyNyAyMTguNjU2QzIyNyAyMTIuODgyIDIyOC45ODQgMjA3Ljk5NSAyMzIuOTUyIDIwMy45OTdDMjM3LjE0IDE5OS45OTkgMjQxLjk5IDE5OCAyNDcuNSAxOThDMjUzLjIzMSAxOTggMjU4LjA4IDE5OS45OTkgMjYyLjA0OSAyMDMuOTk3QzI2Ni4wMTYgMjA3Ljk5NSAyNjggMjEyLjg4MiAyNjggMjE4LjY1NkMyNjggMjI0LjIwOSAyNjYuMDE2IDIyOS4wOTUgMjYyLjA0OSAyMzMuMzE2QzI1OC4wOCAyMzcuMzEzIDI1My4yMzEgMjM5LjMxMyAyNDcuNSAyMzkuMzEzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTMxOS4zMzMgNDEzSDI4OFYyNDkuNjc2SDMxNlYyNzcuMjMzQzMxOS4zMzMgMjY4LjEwNCAzMjUuNzc4IDI2MC4zNjQgMzM0LjY2NyAyNTQuMzUyQzM0My43NzggMjQ4LjExNyAzNTQuNzc4IDI0NSAzNjcuNjY3IDI0NUMzODIuMTExIDI0NSAzOTQuMTEyIDI0OC44OTcgNDAzLjY2NyAyNTYuNjlDNDEzLjIyMiAyNjQuNDg0IDQxOS40NDQgMjc0LjgzNyA0MjIuMzM0IDI4Ny43NTJINDE2LjY2N0M0MTguODg5IDI3NC44MzcgNDI1IDI2NC40ODQgNDM1IDI1Ni42OUM0NDUgMjQ4Ljg5NyA0NTcuMzM0IDI0NSA0NzIgMjQ1QzQ5MC42NjYgMjQ1IDUwNS4zMzQgMjUwLjQ1NSA1MTYgMjYxLjM2NkM1MjYuNjY3IDI3Mi4yNzYgNTMyIDI4Ny4xOTUgNTMyIDMwNi4xMjFWNDEzSDUwMS4zMzNWMzEzLjgwNEM1MDEuMzMzIDMwMC44ODkgNDk4IDI5MC45ODEgNDkxLjMzMyAyODQuMDc4QzQ4NC44ODkgMjc2Ljk1MiA0NzYuMTExIDI3My4zOSA0NjUgMjczLjM5QzQ1Ny4yMjIgMjczLjM5IDQ1MC4zMzMgMjc1LjE3MSA0NDQuMzM0IDI3OC43MzRDNDM4LjU1NiAyODIuMDc0IDQzNCAyODYuOTcyIDQzMC42NjcgMjkzLjQzQzQyNy4zMzMgMjk5Ljg4NyA0MjUuNjY3IDMwNy40NTcgNDI1LjY2NyAzMTYuMTQxVjQxM0gzOTQuNjY3VjMxMy40NjlDMzk0LjY2NyAzMDAuNTU1IDM5MS40NDUgMjkwLjc1OCAzODUgMjg0LjA3OEMzNzguNTU2IDI3Ny4xNzUgMzY5Ljc3OCAyNzMuNzI0IDM1OC42NjcgMjczLjcyNEMzNTAuODg5IDI3My43MjQgMzQ0IDI3NS41MDUgMzM4IDI3OS4wNjhDMzMyLjIyMiAyODIuNDA4IDMyNy42NjcgMjg3LjMwNyAzMjQuMzMzIDI5My43NjNDMzIxIDI5OS45OTggMzE5LjMzMyAzMDcuNDU3IDMxOS4zMzMgMzE2LjE0MVY0MTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzExNTlfMzEzIj4KPHJlY3Qgd2lkdGg9IjYxNiIgaGVpZ2h0PSI2MTYiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+&logoColor=white" alt="Sim.ai"></a>

Self-hosted: NPM Package

bash
npx simstudio

β†’ http://localhost:3000

#### Note
Docker must be installed and running on your machine.

#### Options

| Flag | Description |
|------|-------------|
| -p, --port <port> | Port to run Sim on (default 3000) |
| --no-pull | Skip pulling latest Docker images |

Self-hosted: Docker Compose

bash
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d

Open http://localhost:3000

Sim also supports local models via Ollama and vLLM β€” see the Docker self-hosting docs for setup details.

Self-hosted: Manual Setup

Requirements: Bun, Node.js v20+, PostgreSQL 12+ with pgvector

1. Clone and install:

bash
git clone https://github.com/simstudioai/sim.git
cd sim
bun install
bun run prepare # Set up pre-commit hooks

2. Set up PostgreSQL with pgvector:

bash
docker run --name simstudio-db -e POSTGRES_PASSWORD=your_password -e POSTGRES_DB=simstudio -p 5432:5432 -d pgvector/pgvector:pg17

Or install manually via the pgvector guide.

3. Configure environment:

bash
cp apps/sim/.env.example apps/sim/.env

Create your secrets


perl -i -pe "s/your_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_internal_api_secret/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_api_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env

DB configs for migration


cp packages/db/.env.example packages/db/.env

Edit both .env files to set DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"

4. Run migrations:

bash
cd packages/db && bun run db:migrate

5. Start development servers:

bash
bun run dev:full  # Starts Next.js app and realtime socket server

Or run separately: bun run dev (Next.js) and cd apps/sim && bun run dev:sockets (realtime).

Copilot API Keys

Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:

- Go to https://sim.ai β†’ Settings β†’ Copilot and generate a Copilot API key
- Set COPILOT_API_KEY environment variable in your self-hosted apps/sim/.env file to that value

Environment Variables

See the environment variables reference for the full list, or apps/sim/.env.example for defaults.

Tech Stack

- Framework: Next.js (App Router)
- Runtime: Bun
- Database: PostgreSQL with Drizzle ORM
- Authentication: Better Auth
- UI: Shadcn, Tailwind CSS
- State Management: Zustand
- Flow Editor: ReactFlow
- Docs: Fumadocs
- Monorepo: Turborepo
- Realtime: Socket.io
- Background Jobs: Trigger.dev
- Remote Code Execution: E2B

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

<p align="center">Made with ❀️ by the Sim Team</p>