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 the maintainers via direct message on our Discord community.
- 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
Applies to documentation, code comments, UI strings, commit messages, and script output:
- No em-dashes. Use hyphens, commas, or restructure the sentence.
- No emojis or decorative unicode symbols anywhere in the project.
- No filler phrasing ("seamless", "leverage", "delve", "robust", "empower", and similar). Write plain, precise, technical prose.
- UI icons come exclusively from lucide-react. Never use emoji or unicode symbols as icons.
| 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/*)
Read DESIGN.md before writing or changing UI. It is the rulebook for tokens, colour bindings, typography, spacing, icons, components and charts. The short version:
- No raw values. Every colour, font size, spacing value, radius, shadow and icon size comes from the token layer in
apps/web/src/app/globals.css. If a value you need is not a token, the answer is a token, not a hex. - Colour carries meaning. Alert severity uses
sev-high/sev-medium/sev-low. A measured quantity keeps its permanentsig-*colour wherever it appears (NDVI is alwayssig-vegetation, rainfall is alwayssig-precip). Anonymous chart series useviz-1throughviz-8in order. - Raster ramps are data. The
--ramp-vegetationand--ramp-waterstops must match the TiTiler colormap exactly. Never restyle them to fit a theme. - Charts compose the shared base. Use the fragments in
components/charts/chart-base.tsrather than restating grid, axis or tooltip options. - Icons are lucide-react only. Never emoji, never inline unicode symbols as icons.
Visual reference: docs/design/OpenFarm Design System.dc.html and docs/design/OpenFarm Screens.dc.html. Open them in a browser. They use inline styles so they render without a build step - read them for layout and hierarchy, never copy the inline hex out of them.
- 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, in both light and dark
- UI changes follow DESIGN.md: no raw colours, sizes or spacing outside the token layer
-
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
Adding or renaming an environment variable touches four places: .env.example, the compose service that passes it, the Dockerfile if the web app reads it at build time, and the code that consumes it. scripts/check-env-parity.py verifies the chain and runs in the pre-commit hook and CI. It fails when a documented variable reaches no container, when compose interpolates something the example never documents, when a NEXT_PUBLIC_* variable the web app reads is not a build arg (Next.js inlines those at build time, so a missing one silently bakes the fallback into the image), and when a documented API setting never reaches the api or processor that reads it.
python3 scripts/check-env-parity.pyThe 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.