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.py and reference them in your ViewSet's permission_classes attribute to enforce workspace-level or project-level access controls.
  • Query Filtering: Implement django-filter FilterSets in apps/api/plane/api/filters.py, then attach them to your ViewSet using filterset_class = MyModelFilter to enable query-string filtering.
  • Unit Tests: Write tests in apps/api/plane/tests/unit/ following the pattern of existing files like test_issue.py. Use pytest.mark.django_db and DRF's APIClient to test authentication, serialization, and CRUD operations.

Summary

  • Serializers live in apps/api/plane/api/serializers.py and handle model-to-JSON conversion using ModelSerializer.
  • ViewSets reside in apps/api/plane/api/views.py and implement business logic through DRF's ModelViewSet class.
  • URL Routing occurs in apps/api/plane/api/urls.py using DefaultRouter, with the project-wide apps/api/plane/urls.py mounting routes at /api/v1/.
  • Authentication follows Plane's existing patterns using permission_classes on 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →