How to Add a New API Endpoint to Plane's Django Backend (apps/api)
To add a new API endpoint to Plane's Django backend, create a DRF serializer in apps/api/plane/api/serializers.py, implement a ViewSet in apps/api/plane/api/views.py, and register the route in apps/api/plane/api/urls.py under the api/v1/ prefix.
Plane is an open-source project management tool built on Django and Django REST Framework (DRF). All public API routes are mounted under api/v1/ and defined within the apps/api/plane/api/ package, following standard DRF conventions for serializers, viewsets, and routers.
Create a Django REST Framework Serializer
Start by defining a serializer that converts your model instances to JSON and validates incoming data. Create or edit apps/api/plane/api/serializers.py to add a ModelSerializer for the target model.
# apps/api/plane/api/serializers.py
from rest_framework import serializers
from plane.models import MyModel # Replace with your actual model
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ("id", "name", "created_at", "updated_at")
Plane stores its DRF serializers in this centralized location to maintain consistency with existing endpoints for issues, labels, and cycles. This structure allows the test suite to discover serializers automatically and keeps the codebase organized.
Implement the ViewSet Business Logic
Next, create a view that contains the business logic for your endpoint. Add a ModelViewSet to apps/api/plane/api/views.py to automatically provide list, retrieve, create, update, and destroy actions.
# apps/api/plane/api/views.py
from rest_framework import viewsets, permissions
from plane.models import MyModel
from .serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
permission_classes = [permissions.IsAuthenticated]
This pattern mirrors existing viewsets in the Plane repository, such as the issue viewset. Using ModelViewSet eliminates boilerplate code while maintaining full CRUD functionality. Adjust the permission_classes list to match your security requirements—Plane typically uses IsAuthenticated or custom permission classes defined in apps/api/plane/api/permissions.py.
Register the Route in the API Router
Expose the new endpoint by registering the ViewSet with a DRF router in apps/api/plane/api/urls.py.
# apps/api/plane/api/urls.py
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import MyModelViewSet
router = DefaultRouter()
router.register(r"my-models", MyModelViewSet, basename="my-model")
urlpatterns = [
path("", include(router.urls)),
]
The top-level URL configuration in apps/api/plane/urls.py already includes this module at the api/v1/ prefix:
# apps/api/plane/urls.py
urlpatterns = [
path("api/v1/", include("plane.api.urls")),
]
Because the router attaches to an empty string, your final endpoint URLs become:
GET /api/v1/my-models/(list)POST /api/v1/my-models/(create)GET /api/v1/my-models/<id>/(retrieve)PUT /api/v1/my-models/<id>/(update)DELETE /api/v1/my-models/<id>/(destroy)
Add Permissions, Filtering, and Tests
For production-ready endpoints, extend the basic implementation with these additional components:
- Custom Permissions: Create permission classes in
apps/api/plane/api/permissions.pyand reference them in your ViewSet'spermission_classesattribute to enforce workspace-level or project-level access controls. - Query Filtering: Implement
django-filterFilterSets inapps/api/plane/api/filters.py, then attach them to your ViewSet usingfilterset_class = MyModelFilterto enable query-string filtering. - Unit Tests: Write tests in
apps/api/plane/tests/unit/following the pattern of existing files liketest_issue.py. Usepytest.mark.django_dband DRF'sAPIClientto test authentication, serialization, and CRUD operations.
Summary
- Serializers live in
apps/api/plane/api/serializers.pyand handle model-to-JSON conversion usingModelSerializer. - ViewSets reside in
apps/api/plane/api/views.pyand implement business logic through DRF'sModelViewSetclass. - URL Routing occurs in
apps/api/plane/api/urls.pyusingDefaultRouter, with the project-wideapps/api/plane/urls.pymounting routes at/api/v1/. - Authentication follows Plane's existing patterns using
permission_classeson ViewSets. - Testing should mirror existing unit tests in
apps/api/plane/tests/unit/using pytest and the Django database marker.
Frequently Asked Questions
What file structure does Plane use for API endpoints?
Plane organizes its Django REST Framework components within apps/api/plane/api/, with serializers.py for data validation, views.py for business logic, and urls.py for routing. The project-wide URL configuration in apps/api/plane/urls.py includes these routes under the api/v1/ namespace, ensuring all endpoints follow a consistent versioning scheme.
How do I handle permissions for new endpoints in Plane?
Add custom permission classes to apps/api/plane/api/permissions.py if you need workspace-specific or role-based access control. Then import and reference these classes in your ViewSet's permission_classes list. For standard authentication, use permissions.IsAuthenticated from Django REST Framework to require valid user sessions.
Where should I write tests for new API endpoints?
Create test files in apps/api/plane/tests/unit/ following the naming convention test_<model>.py. Use pytest.mark.django_db to enable database access and DRF's APIClient to simulate HTTP requests. Run the test suite using docker compose -f docker-compose-test.yml run --rm api-tests pytest -m unit from the repository root.
How do I access the new endpoint after deployment?
Once registered, your endpoint becomes available at https://<your-domain>/api/v1/<your-route>/ based on the router prefix defined in apps/api/plane/api/urls.py. Ensure your frontend or API client includes proper authentication headers (Bearer tokens or session cookies) as required by the ViewSet's permission classes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →