- Backend (GoLang): cmd/ with pkg/ (GoLang code), and test/.
- Frontend (TypeScript/React): web_src/ built with Vite.
- Tooling: Makefile (common tasks), protos/ (protobuf definitions for the API), scripts/ (protobuf generation), db/ (database structure and migrations).
- Documentation: Markdown files in docs/.
- gRPC API implementation in in pkg/grpc/actions
- Database models in pkg/models
- Setup dev environment:
make dev.setup - Run server:
make dev.start- UI at http://localhost:8000 - One-shot backend tests:
make test(Golang). - Targeted backend tests:
make test PKG_TEST_PACKAGES=./pkg/workers - Targeted E2E tests:
make e2e E2E_TEST_PACKAGES=./test/e2e/workflows - For E2E test authoring, see docs/development/e2e_tests.md
- After updating UI code, always run
make check.build.uito verify everything is correct - After editing JS code, always run
make format.jsto make sure that the files are consistently formatted - After editing Golang code, always run
make format.goto make sure that files are consistently formatted - After updating GoLang code, always check it with
make lint && make check.build.app - NEVER MANUALLY CREATE MIGRATION FILES. ALWAYS use
make db.migration.create NAME=<name>to generate DB migrations. Always use dashes instead of underscores in the name. We do not write migrations to rollback, so leave the*.down.sqlfiles empty. After adding a migration, runmake db.migrate DB_NAME=<DB_NAME>, where DB_NAME can besuperplane_devorsuperplane_test - When validating enum fields in protobuf requests, ensure that the enums are properly mapped to constants in the
pkg/modelspackage. Check theProto*and*ToProtofunctions in pkg/grpc/actions/common.go. - When adding a new worker in pkg/workers, always add its startup to
cmd/server/main.go, and update the docker compose files with the new environment variables that are needed. - After adding new API endpoints, ensure the new endpoints have their authorization covered in
pkg/authorization/interceptor.go - For UI component workflow, see web_src/AGENTS.md
- For new components or triggers, see docs/contributing/component-implementations.md
- For component design guidelines and quality standards, see docs/contributing/component-design.md
- After updating the proto definitions in protos/, always regenerate them, the OpenAPI spec for the API, and SDKs for the CLI and the UI:
make pb.gento regenerate protobuf filesmake openapi.spec.gento generate OpenAPI spec for the APImake openapi.client.gento generate GoLang SDK for the APImake openapi.web.client.gento generate TypeScript SDK for the UI
- Tests end with _test.go
- Always prefer early returns over else blocks when possible
- GoLang: prefer
anyoverinterface{}types - GoLang: when checking for the existence of an item on a list, use
slice.Containsorslice.ContainsFunc - When naming variables, avoid names like
*Stror*UUID; Go is a typed language, we don't need types in the variables names - When writing tests that require specific timestamps to be used, always use timestamps based off of
time.Now(), instead of absolute times created withtime.Date - The name of the application is "SuperPlane", not "Superplane" in all user-facing text (user interfaces, emails, notifications, documentation, etc.).
When working with database transactions, follow these rules to ensure data consistency:
-
NEVER call
database.Conn()inside a function that receives atx *gorm.DBparameter- ❌ Bad:
func process(tx *gorm.DB) { user, _ := models.FindUser(id) }where FindUser callsdatabase.Conn() - ✅ Good:
func process(tx *gorm.DB) { user, _ := models.FindUserInTransaction(tx, id) }
- ❌ Bad:
-
Always propagate the transaction context through the entire call chain
- Pass
txas the first parameter to all functions that need database access - If a model method is used within a transaction, create an
*InTransaction()variant that acceptstx
- Pass
-
Context constructors must accept
tx *gorm.DBif they perform database queries- ❌ Bad:
NewAuthContext(orgID, service)that internally callsdatabase.Conn() - ✅ Good:
NewAuthContext(tx, orgID, service)that uses the passed transaction
- ❌ Bad:
-
When creating new model methods:
- Create both variants:
FindUser()andFindUserInTransaction(tx *gorm.DB) - The non-transaction variant should call the transaction variant:
return FindUserInTransaction(database.Conn(), ...)
- Create both variants:
Why this matters: Using database.Conn() inside transaction contexts breaks isolation, causes data inconsistency on rollback, and can lead to race conditions.