Thank you for your interest in contributing to OpenFarm! Whether it's a bug fix, new feature, documentation improvement, or feedback — every contribution matters. This guide covers everything you need to get up and running.
- Code of Conduct
- Getting Started
- Development Setup
- Project Structure
- Architecture Rules
- Making Changes
- Code Style
- Commit Messages
- Pull Request Process
- Database Migrations
- Internationalization (i18n)
- Testing
- Reporting Issues
- Security Vulnerabilities
- Questions & Discussions
This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to hello@openfarm.earth.
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/<your-username>/OpenFarm.git cd OpenFarm
- Add upstream remote:
git remote add upstream https://github.com/superzero11/OpenFarm.git
- Enable pre-commit hooks (runs lint + type-check before each commit):
git config core.hooksPath .husky
- Create a branch from
main:git checkout -b feat/your-feature-name
| Tool | Version | Purpose |
|---|---|---|
| Docker & Docker Compose | v2+ | Full-stack orchestration |
| Node.js | 20+ | Frontend development |
| npm | 10+ | Package management |
| Python | 3.11+ | Backend development |
| pip | latest | Python package management |
| Google OAuth credentials | — | Setup guide |
# 1. Copy and configure environment
cp .env.example .env
# 2. Generate secrets
openssl rand -base64 32 # → paste as NEXTAUTH_SECRET
openssl rand -base64 64 # → paste as OPENFARM_JWT_SECRET
# 3. Edit .env — fill in GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET
# 4. Start everything (dev mode — exposes ports to localhost)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --buildServices will be available at:
| Service | URL | Purpose |
|---|---|---|
| Web UI | http://localhost:3000 | Frontend |
| API (Swagger) | http://localhost:8000/docs | Interactive API docs |
| TiTiler | http://localhost:8080 | COG tile server |
| MinIO Console | http://localhost:9001 | Object storage admin |
| PostgreSQL | localhost:5432 | Database |
| Redis | localhost:6379 | Queue broker |
Verify everything is healthy:
curl http://localhost:8000/healthz # API
curl http://localhost:8080/healthz # TiTiler
curl http://localhost:3000/api/health # Webcd apps/web
npm install
npm run dev # → http://localhost:3000
npm run lint # ESLint checks
npm run type-check # TypeScript strict modeNote: You'll need the API running (via Docker or locally) for the frontend to function fully.
cd services/api
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
alembic upgrade head
uvicorn app.main:app --reload --port 8000Note: Requires PostgreSQL + PostGIS and Redis running locally or via Docker.
openfarm/
├── apps/web/ → Next.js 14 frontend
│ ├── src/
│ │ ├── app/ → App Router pages & layouts
│ │ ├── components/ → React components (UI, map, charts)
│ │ ├── i18n/ → Internationalization config
│ │ ├── lib/ → API client, auth, utilities
│ │ └── types/ → TypeScript type declarations
│ └── messages/ → i18n translation files (en.json, es.json)
├── services/api/ → FastAPI backend
│ ├── app/
│ │ ├── core/ → Config, database engines, logging
│ │ ├── middleware/ → JWT auth + RBAC dependencies
│ │ ├── models/ → SQLAlchemy ORM models (all 13 tables)
│ │ ├── routers/ → API route handlers
│ │ ├── schemas/ → Pydantic v2 request/response schemas
│ │ ├── tasks/ → Celery background tasks (NDVI pipeline)
│ │ └── worker.py → Celery app configuration
│ └── alembic/ → Database migration scripts
├── services/tiler/ → TiTiler COG tile server (shared JWT auth)
├── docker-compose.yml → Full stack orchestration
├── .env.example → Environment variable template
└── docs/ → Internal documentation (not in repo)
These are strict invariants — please follow them in all contributions:
-
Next.js ↔ Postgres isolation: The Next.js app talks to Postgres only for user upsert during the NextAuth auth callback (
src/lib/db.ts). All other data flows through the FastAPI API viasrc/lib/api.ts. -
API calls: Always use
apiFetch()fromlib/api.ts— never call the API directly withfetch(). -
Org-scoped resources: All org-scoped API endpoints require the
X-Org-Idheader, validated by theget_org_contextdependency. -
Soft-delete pattern: Use
deleted_attimestamp — filter with.where(Model.deleted_at.is_(None)). -
UUID primary keys: All tables use UUID PKs with
server_default=uuid_generate_v4(). -
Geometry: Stored as
MultiPolygon(4326)— auto-wrapPolygoninputs toMultiPolygonusing the_geojson_to_multi()helper. -
No hardcoded URLs or secrets: Everything via environment variables.
-
Create a branch from
main:git checkout -b feat/your-feature-name
-
Make your changes — keep commits focused and atomic.
-
Test locally — ensure the full stack runs:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
-
Lint and type-check before pushing:
# Frontend cd apps/web npm run lint npm run type-check # Backend cd services/api ruff check . ruff format --check . mypy app/ --ignore-missing-imports
-
Build to catch production issues:
# Frontend production build cd apps/web && npm run build # Docker full-stack build docker compose -f docker-compose.yml -f docker-compose.dev.yml build
| Aspect | Standard |
|---|---|
| Formatter / Linter | Ruff (configured via pyproject.toml) |
| Type hints | Required on all function signatures |
| Line length | 88 characters (Ruff default) |
| Imports | Sorted by Ruff (isort-compatible) |
- Async: FastAPI routes use
async defwithAsyncSession; Celery tasks use sync sessions (database_sync.py) - Schemas: Pydantic v2 with
model_config = {"from_attributes": True}for ORM conversion - Logging:
structlog— use structured key-value pairs, not string interpolation:# ✅ Good logger.info("farm_created", farm_id=str(farm.id), org_id=str(ctx.org_id)) # ❌ Bad logger.info(f"Farm {farm.id} created in org {ctx.org_id}")
| Aspect | Standard |
|---|---|
| Linter | ESLint (Next.js config) |
| Type checking | TypeScript strict mode |
| Styling | Tailwind CSS + cn() utility from lib/utils.ts |
| Components | React Server Components by default; add "use client" only when needed |
- API calls: Always use
apiFetch()fromlib/api.ts— neverfetch()directly - i18n: All user-facing strings must be in
messages/en.jsonandmessages/es.json - Imports: Use
@/*path alias (maps to./src/*)
- No
console.login production code (use proper logging) - No
anytypes in TypeScript unless absolutely unavoidable (with a// eslint-disablecomment explaining why) - Prefer named exports over default exports
- Keep files focused — one component/module per file
Follow Conventional Commits:
<type>(<scope>): <short description>
[optional body]
[optional footer — e.g., Closes #123]
Types: feat, fix, docs, style, refactor, test, chore, ci, perf
Scopes: web, api, tiler, docker, db, ci
Examples:
feat(api): add field boundary import from KML
fix(web): handle expired JWT token refresh
docs: update README with self-hosting guide
chore(docker): pin PostGIS image version
ci: add lint and type-check to CI pipeline
perf(api): add index on fields.org_id for tenant queries
- Rebase on latest
main:git fetch upstream git rebase upstream/main
- Run all checks locally:
# Frontend cd apps/web && npm run lint && npm run type-check && npm run build # Backend cd services/api && ruff check . && ruff format --check .
- Push your branch and open a PR against
main
- PR has a clear title following conventional commit format
- Description explains what changed and why
- Lint, type-check, and build pass
- Screenshots included for UI changes
-
messages/en.jsonandmessages/es.jsonupdated if adding user-facing strings - Database migration added if schema changed (
alembic revision --autogenerate) - Related issue referenced with
Closes #123orFixes #123
- A maintainer will review your PR (usually within 48 hours)
- Address feedback via additional commits (no force-push during review)
- Once approved, the maintainer will squash-and-merge
- Small PRs (< 300 lines) are reviewed faster and merged sooner
- Split large features into stacked PRs when possible
- One feature or fix per PR — avoid bundling unrelated changes
When you modify ORM models in services/api/app/models/tables.py:
cd services/api
# 1. Generate migration
alembic revision --autogenerate -m "describe your change"
# 2. Review the generated file in alembic/versions/
# 3. Apply migration
alembic upgrade headMigration rules:
- Always review auto-generated migrations — they may miss some changes
- Never modify an existing migration that has been merged to
main - Include both
upgrade()anddowngrade()functions - Test the full upgrade + downgrade cycle locally
OpenFarm uses next-intl with en and es locales.
- Translation files:
apps/web/messages/en.jsonandapps/web/messages/es.json - Routes use the
[locale]segment withlocalePrefix: "as-needed" - All user-facing strings must be translated — no hardcoded English in components
When adding new strings:
- Add the key to both
en.jsonandes.json - Use the
useTranslations()hook in client components - Use
getTranslations()in server components
Before submitting a PR, verify:
- Full stack starts cleanly with
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build - Health endpoints respond:
/healthz(API),/api/health(Web) - Google OAuth sign-in flow works end-to-end
- Core workflows function: create farm → add field → trigger NDVI job
- No console errors in browser DevTools
- No unhandled exceptions in API logs
The CI pipeline runs on every PR:
- Frontend:
npm run lint→npm run type-check - Backend:
ruff check .→ruff format --check .
All checks must pass before merge.
Use GitHub Issues with the provided templates:
- Bug reports: Include steps to reproduce, expected vs actual behavior, environment details, and relevant logs
- Feature requests: Describe the use case, proposed solution, and any alternatives considered
Do NOT open a public issue for security vulnerabilities.
Please see SECURITY.md for our responsible disclosure policy.
- Open a Discussion for questions, ideas, or general feedback
- Join the conversation — we're happy to help you get started
Thank you for contributing to OpenFarm! Every contribution helps bring transparent, affordable crop intelligence to farms everywhere. 🌾