This document provides essential guidance for AI agents contributing to the Polar codebase. Imagine this file as a new joiner to the team who needs to understand the coding standards, practices, and conventions used in this repository.
- Do not add comments to the code unless necessary. The code should be self-explanatory.
- Use meaningful variable and function names.
- Follow good practices and code conventions.
- Make sure that all the new code is maintanable and follows the SOLID principles.
- Do not modify unrelated code to the task or issue you are working on.
Polar is a payment infrastructure platform with a monorepo structure.
server/: The backend is a Python application built with the FastAPI framework.- Database: It uses PostgreSQL as its database, with SQLAlchemy as the ORM. Database models are located in
server/polar/models. - Background Jobs: Asynchronous tasks are handled by Dramatiq workers.
- API: The core API logic is in
server/polar/, with routes organized into modules.
- Database: It uses PostgreSQL as its database, with SQLAlchemy as the ORM. Database models are located in
clients/: The frontend applications are managed with Turborepo and pnpm.clients/apps/web/: The main web dashboard application built with Next.js.clients/apps/app/: iOS and Android app built with Expo and React Native.clients/packages/ui/: A shared library of React components built with Radix UI and Tailwind CSS.clients/packages/client/: The generated API client and data-fetching hooks.
dev/: Contains scripts and tools for development.docs/: Contains the documentation of the service, intended as a reference for developers and users. It's generated by Mintlify: https://mintlify.com/docs/llms.txtsdk/overlay: Our official SDK are generated with Speakeasy, a tool for generating SDKs from OpenAPI specifications. We sometimes need to tweak our schema to ensure the generated SDKs work correctly. This directory contains these tweaks, which follow the OpenAPI Overlay specification: https://spec.openapis.org/overlay/latest.html
The primary setup and workflow instructions are in DEVELOPMENT.md.
- Backend: In the
server/directory, run the following commands in separate terminals:uv run task apito start the FastAPI server (http://127.0.0.1:8000).uv run task workerto start the Dramatiq background worker.
- Frontend: In the
clients/directory, runpnpm run devto start the Next.js development server (http://127.0.0.1:3000).
The project uses Alembic for database migrations, located in server/migrations/. To apply migrations, run uv run task db_migrate from the server/ directory. When creating a new model, you'll need to generate a new migration script.
A migration script can be generated automatically from the models changes using the following command, which should be run from the server/ directory:
uv run alembic revision --autogenerate -m "<description>"If you need to create an empty migration script, which can be useful if we need to perform data migrations, use:
uv run alembic revision -m "<description>"The backend requires to be linted and type-checked. To do so, run:
uv run task lint && uv run task lint_typesBackend tests are located in the tests/ directory. It uses pytest for testing. To run the tests, use:
uv run task testTo run a specific test file:
uv run pytest tests/path/to/test_file.pyTo run a specific test class or method, use the :: syntax:
uv run pytest tests/path/to/test_file.py::TestClassName::test_method_nameCRITICAL: Always prefix Python commands with uv run when working in the Polar environment. This ensures:
- The correct Python version (3.14) is used
- All project dependencies are available
- Environment variables are properly loaded
- Commands run in the correct virtual environment context
- Modular Structure: The code is organized in a modular way, with each module in its own folder under
server/polar/. A typical module contains:endpoints.py: API endpoints.service.py: Business logic, encapsulated in service classes.schemas.py: Pydantic schemas for API request/response validation and serialization.repository.py: Database query logic, using SQLAlchemy.
- Models: Note that SQLAlchemy models are an exception to the modular structure and are defined globally in
server/polar/models. - API Client Generation: The frontend's TypeScript client is generated from the backend's OpenAPI schema. After making changes to the API, you may need to run
pnpm run generateinclients/packages/clientto update the client.
- imports at module top: Import statements like
from sqlalchemy import update, selectshould be at the top of the file, not inside methods or functions. - Follow modular structure: Import models from
polar.models, services from their respective modules, following the established patterns. - Dependency injection: Use FastAPI's dependency injection system for repositories and services.
-
Accept domain objects over IDs: Repository methods should prefer accepting domain objects (e.g.,
Order) instead of just UUIDs when the calling code already has the object.# Preferred async def release_payment_lock(self, order: Order, *, flush: bool = False) -> Order: return await self.update(order, update_dict={"payment_lock_acquired_at": None}, flush=flush) # Avoid when object is available async def release_payment_lock(self, order_id: UUID) -> None: # ...
-
Return updated objects: Repository methods should return updated domain objects to provide the caller with the latest state.
-
Use flush parameters: Include
flush: bool = Falseparameters for controlling transaction boundaries. It should always be a keyword argument, after*to avoid confusion. -
Inherit from RepositoryBase: Follow the established repository inheritance patterns.
- Proper exception handling: Use appropriate HTTP status codes in custom exceptions:
409for conflicts (e.g.,PaymentAlreadyInProgress)422for validation errors404for not found errors
class PaymentAlreadyInProgress(OrderError): def __init__(self, order: Order) -> None: self.order = order message = f"Payment for order {order.id} is already in progress" super().__init__(message, 409) # Include status code
- Meaningful error messages: Include relevant entity IDs and context in error messages for debugging.
- Async patterns: Use proper async/await patterns with SQLAlchemy sessions.
- Session management: Use dependency injection for database sessions, don't create sessions manually.
- Inherit from appropriate base classes: Custom exceptions should inherit from domain-specific error classes (e.g.,
OrderError). - Include HTTP status codes: Pass status codes as the second parameter to the parent constructor for known errors.
- Contextual information: Store relevant domain objects as attributes for error handling and logging.
- Use ORM patterns consistently: Prefer SQLAlchemy ORM methods over raw SQL.
- Session management: Use
AsyncSessionwith proper dependency injection. - Repository patterns: Encapsulate database logic in repository classes inheriting from
RepositoryBase.
In most cases, you should never call session.commit() directly in business logic. We have established patterns for that: the API backend automatically commits the session at the end of each request, and background workers commit the session at the end of each task. It avoids to have a database in an inconsistent state in case of exceptions. If you have a session.commit() in your code, it's likely a mistake. Otherwise, please explicitly document why it's necessary.
If you need to ensure that data is flushed to the database, to run constraints or fill server defaults, use session.flush() instead. Bear in mind though that it might not be necessary, as SQLAlchemy automatically flushes pending changes before read operations.
- Test file structure: Test files mirror the source code structure. If you have
server/polar/foo/endpoints.py, the corresponding tests will be intests/foo/test_endpoints.py. - Avoid redundant fixture setup: Don't manually set data that fixtures already provide (e.g.,
customer.stripe_customer_idwhen thecustomerfixture includes it). - Descriptive test names: Use method names that clearly describe the behavior being tested.
- Encapsulate test logic: Use class based tests. Usually we have one class per method that we want to test, and each test case is a different scenario for that method.
- test_task and test_endpoints: are an E2E test where mocking is not used. They should be used to test the actual behavior of the application, including database interactions and external services if possible.
- Proper mocking: Mock external services (Stripe, etc.) using the established patterns with
MagicMock. - Use existing fixtures: Leverage
SaveFixture,AsyncSession, and other established test utilities. - Test structure: Follow the existing patterns with
pytest.mark.asyncioand class-based test organization.
When adding or modifying tax ID validators in server/polar/tax/tax_id.py:
- Keep validators minimal: Do not add lengthy docstrings to validator classes. The code should be self-explanatory.
- Follow existing patterns: Use the same structure as other validators (e.g.,
CLTINValidator,TRTINValidator). - Use stdnum library: Leverage the
stdnumlibrary for validation when a module exists for the tax ID type. - Minimal tests: Add a few representative valid format tests and only one invalid test case per tax ID type. Do not add excessive negative tests.
Translation files are located in clients/packages/i18n/src/locales/. When adding new translatable strings, only add them to en.ts. Do not manually edit any other locale files (e.g., fr.ts, de.ts, sv.ts). A CI job automatically translates new English strings into all supported languages and commits the results to the branch. After pushing changes to en.ts, pull the branch once the CI translation job completes.
- Modular Structure: The code is organized in a modular way, with features grouped into their own folders.
- Key Page Directories:
apps/web/src/app/(main)/dashboard: The main dashboard for logged-in users.apps/web/src/app/(main)/[organization]: Organization-specific pages.
- Data Fetching: The frontend uses TanStack Query for data fetching. Hooks are generated by
openapi-typescript-codegenand are available from the@polar-sh/sdkpackage. - State Management: Global state is managed with Zustand.
- UI Components: Use components from the shared
clients/packages/uilibrary whenever possible. These components are built on top of Tailwind CSS and Radix UI. - Styling: Use Tailwind CSS for styling.
- Best Practices: The code follows React and Next.js best practices.
The backend uses a custom authentication system built on FastAPI's dependency injection.
-
AuthSubject: The core of the system is theAuthSubject[T]type, which represents the authenticated entity.Tcan beUser,Organization,Customer, orAnonymous. It's available in endpoint signatures as a dependency. If an endpoint does not have anauth_subjectdependency, it is public and accessible to anonymous users. -
Module-Specific Authentication: For most API, authentication models should be defined within each module's
auth.pyfile. This allows creating authenticators with specific scopes and allowed subjects.# server/polar/discount/auth.py _DiscountWrite = Authenticator( required_scopes={Scope.web_default, Scope.discounts_write}, allowed_subjects={User, Organization}, ) DiscountWrite = Annotated[AuthSubject[User | Organization], Depends(_DiscountWrite)]
-
Specific Cases: For API endpoints that should only be used in the context of our web dashboard or our internal backoffice, use one of the predefined authenticator dependencies from
server/polar/auth/dependencies.py.WebUser: Requires a logged-in user (AuthSubject[User]).WebUserOrAnonymous: Allows either a logged-in user or an anonymous user (AuthSubject[User | Anonymous]).AdminUser: Requires a user with admin privileges.
-
Scopes: Scopes are used to control access to specific operations. An
AuthSubjecthas a set of scopes, and anAuthenticatorcan define a set ofrequired_scopes. Access is granted if the subject possesses at least one of the required scopes. -
Example:
from polar.models import User from polar.discount.auth import DiscountWrite @router.post("/discounts") def create_discount(auth_subject: DiscountWrite) -> Discount: # Only users/orgs with `web_default` or `discounts_write` can access this ...
-
How it works: The system checks for credentials in a specific order: customer session token, user session cookie, and various API tokens (OAuth2, Personal Access, Organization Access). If no valid credential is found, it defaults to an
Anonymoussubject. The endpoint's authenticator then validates if the resolved subject type and its scopes are allowed.
- Stripe: Handles payments and subscriptions. Requires API keys and a webhook secret in
server/.env. - GitHub: Used for authentication and repository-related features. Requires a GitHub App to be configured for local development.
The documentation is located in the docs/ directory and is generated by Mintlify. It serves as a reference for developers and users.
From the docs/ directory, it can be built and served locally with:
pnpm dev