# How to Contribute to Open-SEO: A Complete Guide for Developers

> Learn how to contribute to Open-SEO with this developer guide. Set up your environment, run migrations, and submit pull requests to the every-app/open-seo repository.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-08-02

---

**Contributing to the every-app/open-seo repository requires setting up a local TanStack React Start environment with Cloudflare Workers, running database migrations, and submitting single-purpose pull requests that pass lint, type-check, and build validation.**

The **Open-SEO** platform is an open-source, pay-as-you-go SEO tool built by [every-app](https://github.com/every-app) using modern edge-native architecture. This guide walks through cloning the repo, configuring local development, understanding the codebase structure, and successfully submitting your first contribution.

## Prerequisites and Initial Setup

Before you can contribute to Open-SEO, ensure you have Node.js 18+, [pnpm](https://pnpm.io/) (via corepack), and Git installed.

Clone the repository and install dependencies:

```bash
git clone https://github.com/every-app/open-seo.git
cd open-seo
corepack enable
pnpm install --frozen-lockfile

```

The `--frozen-lockfile` flag guarantees you use the exact dependency versions specified in the project's lockfile, preventing environment drift.

## Configure Your Local Development Environment

Open-SEO supports multiple database backends and authentication modes. For local development, you'll use **SQLite via D1** with authentication disabled.

Create your environment file:

```bash
cp .env.example .env.local

```

Add your DataForSEO API credentials (required for rank tracking and keyword research features):

```bash
printf '%s' 'YOUR_LOGIN:YOUR_PASSWORD' | base64

```

Paste the base64 output into `.env.local` as `DATAFORSEO_API_KEY`, then set the auth mode:

```bash
echo "AUTH_MODE=local_noauth" >> .env.local

```

This bypasses Cloudflare Access for local testing, routing all requests through the `local_noauth` handler defined in [`src/lib/auth-mode.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-mode.ts).

## Database Setup and Development Server

Initialize the database schema:

```bash
pnpm run db:migrate:local

```

Migrations are managed via **Drizzle ORM** and configured in [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts) for both D1 (SQLite) and Postgres backends.

Start the development environment:

```bash
pnpm dev:agents

```

This launches the app on `http://open-seo.localhost:1355` using the **portless** Cloudflare Workers local simulation. Alternatively, run `pnpm dev` for a simpler Vite-based dev loop without agent features.

## Understanding the Open-SEO Architecture

To contribute effectively, you need to know how requests flow through the system:

| Component | Location | Purpose |
|-----------|----------|---------|
| **React Start entry point** | [`src/start.ts`](https://github.com/every-app/open-seo/blob/main/src/start.ts) | Initializes server-function middleware and CSRF protection |
| **Worker request handler** | [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) | Routes HTTP traffic, authenticates agents, executes scheduled tasks |
| **MCP transport** | [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) | Exposes JSON-RPC API for AI agents (Claude Code, OpenClaw, Hermes) |
| **Durable Objects** | [`src/server/features/onboarding/OnboardingChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/onboarding/OnboardingChatAgent.ts) | Real-time chat channels for onboarding and SAM agents |
| **Database layer** | [`src/db/index.ts`](https://github.com/every-app/open-seo/blob/main/src/db/index.ts) | Provides scoped Postgres clients via `withPgClient` wrapper |
| **Scheduled rank checks** | [`src/server/features/rank-tracking/services/scheduledRankChecks.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/services/scheduledRankChecks.ts) | Daily cron job for updating tracked rankings |

The **MCP (Managed-Code-Protocol)** in `src/server/mcp/*` is the primary extension point for new AI-accessible functionality. Each method follows a standardized schema-validation pattern using **Zod**.

## Contribution Workflow: Single-Purpose PRs

Open-SEO enforces a **single-purpose PR philosophy**: one logical change per pull request. This keeps reviews focused and history clean.

Follow this workflow when you contribute to Open-SEO:

1. **Open an issue** describing your proposed change (optional but recommended for significant features)
2. **Create a feature branch** from `main`: `git checkout -b feat/descriptive-name`
3. **Implement your change** with appropriate tests
4. **Run local CI checks** before pushing:

```bash
pnpm ci:check       # Linting and type-checking

pnpm test:ci        # Test suite

pnpm vite build     # Production build verification

```

If you modified files under `web/`, also run the web-specific checks documented in [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md).

5. **Push and open a pull request**. The CI pipeline automatically re-runs these checks; passing status is required for merge.

## Code Example: Adding a New MCP Method

Here's how to extend the platform with a new AI-accessible endpoint. This example adds a `searchKeywords` method that wraps the DataForSEO keyword research API.

Create the method implementation:

```typescript
// src/server/mcp/methods/searchKeywords.ts
import { z } from "zod";
import { fetchDataForSeo } from "@/server/dataforseo/client";

export const searchKeywords = {
  input: z.object({
    query: z.string(),
    language: z.string().optional(),
    location: z.string().optional(),
  }),

  async resolve({ query, language = "en", location = "us" }) {
    const response = await fetchDataForSeo("keyword_research", {
      keyword: query,
      language,
      location,
    });
    return response;
  },
};

```

Register the method in the transport layer:

```typescript
// src/server/mcp/transport.ts
import { searchKeywords } from "./methods/searchKeywords";

export const mcpMethods = {
  // existing methods...
  searchKeywords,
};

```

Add corresponding tests in [`src/server/mcp/__tests__/searchKeywords.test.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/__tests__/searchKeywords.test.ts) to verify schema validation and client invocation. Run `pnpm test:ci` to confirm coverage.

## Key Files Every Contributor Should Know

| File | Why it matters for contributions |
|------|--------------------------------|
| [`README.md`](https://github.com/every-app/open-seo/blob/main/README.md) | Project overview, live demo, community links |
| [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md) | Official guidelines, CI commands, PR etiquette |
| [`docs/LOCAL_DEVELOPMENT.md`](https://github.com/every-app/open-seo/blob/main/docs/LOCAL_DEVELOPMENT.md) | Detailed environment setup and auth mode reference |
| [`src/start.ts`](https://github.com/every-app/open-seo/blob/main/src/start.ts) | Server middleware configuration entry point |
| [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) | Core request routing and Worker orchestration |
| [`package.json`](https://github.com/every-app/open-seo/blob/main/package.json) | Runtime dependencies (`@tanstack/react-start`, `zod`) and npm scripts |
| [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts) | Database schema management for D1 and Postgres |
| `scripts/*` | Data seeding, migration utilities, release tools |

These files provide the context needed to understand how Open-SEO boots, how requests are processed, and where to place new functionality.

## Summary

- **Clone and install**: Use `corepack enable` and `pnpm install --frozen-lockfile` for reproducible builds
- **Configure locally**: Set `AUTH_MODE=local_noauth` and base64-encode your DataForSEO credentials
- **Understand the flow**: Requests enter through [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts), MCP methods extend functionality via [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts)
- **Follow the workflow**: Single-purpose PRs, local CI checks (`pnpm ci:check`, `pnpm test:ci`, `pnpm vite build`), then push
- **Extend via MCP**: New AI-accessible features follow the Zod schema + async resolve pattern shown in the `searchKeywords` example

## Frequently Asked Questions

### What authentication mode should I use for local development?

Use `AUTH_MODE=local_noauth` in your `.env.local` file. This mode, implemented in [`src/lib/auth-mode.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-mode.ts), skips Cloudflare Access validation and allows direct API testing without enterprise identity provider setup.

### How do I add a new API endpoint that AI agents can call?

Implement a new method in `src/server/mcp/methods/` following the `{ input: z.Schema, resolve: async fn }` pattern, then register it in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts). The MCP transport automatically exposes registered methods as JSON-RPC endpoints that Claude Code and other agents can discover and invoke.

### What checks must pass before my PR can merge?

The CI pipeline enforces three validations: `pnpm ci:check` (ESLint and TypeScript), `pnpm test:ci` (unit tests), and `pnpm vite build` (production bundle). Web-specific checks from [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md) apply if you modified files under `web/`.

### Can I run Open-SEO with Postgres instead of D1?

Yes. The database layer in [`src/db/index.ts`](https://github.com/every-app/open-seo/blob/main/src/db/index.ts) uses a `withPgClient` wrapper that provides scoped Postgres clients when configured. The same Drizzle migrations work for both D1 (SQLite) and Postgres backends—adjust your connection string in environment variables to switch.