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

> Learn how to contribute to the open-seo project. Follow our guide to submit issues, set up local development with Docker, and reference the roadmap for feature proposals.

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

---

**Start by opening a well-written issue describing the problem and desired behavior, then use the provided Docker Compose setup for local development and reference the public roadmap before proposing features.**

Open-SEO is a TypeScript-first SEO SaaS platform built on Cloudflare Workers, offering server-side APIs for rank tracking, keyword research, and site audits. Contributing to this project requires understanding its opinionated architecture and the maintainers' workflow preferences. This guide walks you through the codebase structure, development setup, and submission process based on the every-app/open-seo source code.

## Understanding the Open-SEO Architecture

Before contributing to Open-SEO, you need to map where your changes belong. The repository enforces a strict separation of concerns across distinct layers:

| Layer | Purpose | Key Source Files |
|-------|---------|------------------|
| **Entry point** | Cloudflare Worker bootstrap and route registration | [[`src/start.ts`](https://github.com/every-app/open-seo/blob/main/src/start.ts)](https://github.com/every-app/open-seo/blob/main/src/start.ts) |
| **Server functions** | TanStack-style API handlers for all public operations | [[`src/serverFunctions/workspace.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/workspace.ts)](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/workspace.ts), [[`src/serverFunctions/keywords.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/keywords.ts)](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/keywords.ts), [[`src/serverFunctions/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/rank-tracking.ts)](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/rank-tracking.ts) |
| **Business logic** | Service orchestration and external API integration | [`src/shared/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts), [`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts) |
| **Data layer** | Drizzle ORM with SQLite/Postgres compatibility | [[`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts)](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts), `drizzle/` migrations |
| **Frontend** | Vite + React with TanStack Query | [[`web/vite.config.ts`](https://github.com/every-app/open-seo/blob/main/web/vite.config.ts)](https://github.com/every-app/open-seo/blob/main/web/vite.config.ts), `web/` source tree |
| **Infrastructure** | Docker self-hosting and CI | [`Dockerfile.selfhost`](https://github.com/every-app/open-seo/blob/main/Dockerfile.selfhost), [[`compose.yaml`](https://github.com/every-app/open-seo/blob/main/compose.yaml)](https://github.com/every-app/open-seo/blob/main/compose.yaml) |

Every public operation lives in a **server function** under `src/serverFunctions/*.ts`. These functions receive Zod-validated input and return JSON consumed by the React frontend via TanStack Query.

## Setting Up Your Local Open-SEO Development Environment

The fastest way to contribute to Open-SEO is through the Docker Compose self-hosting configuration.

### Prerequisites

- Docker and Docker Compose
- Node.js 20+ (for running tests outside containers)

### Initial Setup

Clone the repository and prepare your environment:

```bash
git clone https://github.com/every-app/open-seo.git
cd open-seo
cp .env.example .env

```

Then launch the full stack:

```bash
docker compose up --build

```

This brings up a local Cloudflare-compatible Worker and a Postgres instance, mirroring production conditions without requiring actual Cloudflare credentials.

### Running Tests

Validate your changes before submission:

```bash
npm run test

```

The test suite includes:
- Unit tests under `src/**/*.test.ts` (Vitest)
- End-to-end specs in `e2e/` (Playwright)

## The Open-SEO Contribution Workflow

### Step 1: Start With an Issue

The maintainers prioritize **well-written issues** over surprise pull requests. A succinct, clearly written issue describing the problem and desired behavior is worth its weight in gold according to the [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md) guidelines.

Before opening, check the [public roadmap](http://openseo.so/roadmap) to avoid duplicate feature requests.

### Step 2: Use the Simple Issue Description Skill

If you have the CLI installed, enforce consistent formatting:

```bash
npx skills add every-app/open-seo --skill simple-issue-description

```

Apply this skill to your issue to match the project's documentation standards.

### Step 3: Understand the PR Policy

**Pull requests are not merged automatically.** In [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md), the maintainers explain that PRs serve mainly as proof-of-concepts to improve the associated issue. They may pull changes themselves or prioritize features based on PR quality.

This means your issue quality matters more than your PR code—focus your energy there first.

## Contributing New Features to Open-SEO

### Adding a Server Function

New API endpoints require a server function in `src/serverFunctions/`. Here's the pattern from [`src/serverFunctions/example.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/example.ts):

```typescript
import { z } from "zod";
import { createServerFn } from "@tanstack/start/server";
import { db } from "../shared/db";

// Input validation
const ExampleInput = z.object({
  projectId: z.string().uuid(),
  payload: z.string(),
});

export const example = createServerFn()
  .input(ExampleInput)
  .handler(async ({ input }) => {
    // Business logic – interact with DB via Drizzle
    await db.exampleTable.insert({
      projectId: input.projectId,
      data: input.payload,
    });

    return { success: true };
  });

```

Key requirements:
- **Zod schemas** for all inputs
- **Drizzle ORM** for database operations
- **JSON responses** for TanStack Query consumption

### Consuming Server Functions from the Frontend

The React frontend uses TanStack Query to call server functions. Here's how to use the example function in a component:

```tsx
import { useMutation } from "@tanstack/react-query";
import { example } from "@/serverFunctions/example";

export const ExampleForm = () => {
  const mutation = useMutation(example);

  const onSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const payload = new FormData(form).get("payload") as string;

    mutation.mutate({ projectId: "…", payload });
  };

  return (
    <form onSubmit={onSubmit}>
      <textarea name="payload" required />
      <button type="submit" disabled={mutation.isLoading}>
        Save
      </button>
    </form>
  );
};

```

### Integrating External SEO APIs

For features requiring Google Search Console, GA4, or Ahrefs data, add logic to the appropriate `src/shared/` service:

- **Google Search Console**: [`src/shared/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts)
- **Google Analytics 4**: [`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts)

These services are thin orchestration layers that keep server functions clean.

## Submitting Your Open-SEO Contribution

Once your changes pass locally:

1. Push to your fork
2. Open a PR referencing the original issue number
3. Include screenshots or short videos for UI changes (strongly recommended in the contribution guide)

The maintainers will review your PR as a reference implementation, potentially integrating it directly or using it to prioritize the feature.

## Summary

- **Architecture**: All public operations live in `src/serverFunctions/*.ts` with Zod validation and Drizzle persistence
- **Development**: Use `docker compose up --build` for local setup and `npm run test` for validation
- **Workflow**: Open detailed issues first, use the `simple-issue-description` skill, and treat PRs as proof-of-concepts
- **Requirements**: Check the [roadmap](http://openseo.so/roadmap) before features, include media for UI changes, and ensure tests pass

## Frequently Asked Questions

### Does Open-SEO accept direct pull requests?

**Not automatically.** According to [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md), PRs serve as proof-of-concepts to improve issues rather than direct merge candidates. Maintainers may pull changes themselves or prioritize features based on PR quality. Focus on writing excellent issues first.

### What database does Open-SEO use for local development?

**SQLite via Drizzle ORM**, with **Postgres for production**. The [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts) configuration and Docker Compose setup handle the abstraction, so your code works identically in both environments without modification.

### How do I add a new API endpoint to Open-SEO?

Create a **server function** in `src/serverFunctions/` using `createServerFn()` from TanStack Start. Define a Zod schema for input validation, implement your handler with Drizzle for database operations, and export the function. The frontend consumes it via TanStack Query.

### Where is the Open-SEO contribution guide located?

The complete guidelines are in [[`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md)](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md), covering issue formatting, the PR policy, roadmap links, and development setup instructions.