# How to Create Custom Work Item Properties and Field Types in Plane

> Learn to create custom work item properties and field types in Plane using the customproperty_ prefix and pluggable exporter framework. Enhance your project management capabilities.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: how-to-guide
- Published: 2026-06-18

---

**Plane extends its core work-item schema through the `customproperty_` prefix convention for dynamic filtering and a pluggable exporter framework for custom data types.**

Plane is an open-source project management platform that allows teams to extend the default work-item schema with **custom properties** and specialized **field types**. By leveraging the `customproperty_` prefix convention and the exporter subclassing system, you can store arbitrary data and control how it appears in exports. This guide examines the implementation details found in the makeplane/plane repository to show you exactly how these extension points work.

## Understanding Custom Work Item Properties in Plane

Custom properties in Plane function as dynamic key-value pairs that integrate seamlessly with the existing filter system. Unlike static fields, these properties use a specific naming convention that the WorkItemFiltersAdapter recognizes during query validation.

### The customproperty_ Prefix Convention

Plane identifies custom properties through the **`customproperty_`** prefix. When you define a filter condition using this prefix, the system validates it through the `_isValidWorkItemFilterConditionKey` method in the filter adapter. This method checks that the key starts with `customproperty_` and parses the property name and operator correctly.

In [`packages/shared-state/src/store/work-item-filters/adapter.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/store/work-item-filters/adapter.ts), the adapter translates filters like `customproperty_priority__eq=high` into internal condition nodes that can be applied to database queries. This validation step ensures that only properly formatted custom property keys are processed, preventing invalid filter states from reaching the backend.

### Storage and Backend Implementation

Values for custom properties reside in the **`custom_properties`** JSON field on the issue model. This Django JSONField stores arbitrary key-value pairs, allowing you to add new properties without database migrations.

When creating or updating an issue via the API, you pass custom properties as a nested object:

```typescript
// POST /api/issues/
{
  "title": "Implement authentication",
  "custom_properties": {
    "priority": "high",
    "tshirt_size": "L"
  }
}

```

The backend persists these values in the JSON column, making them available for filtering and display throughout the application.

## Implementing Custom Field Types for Export

While custom properties handle storage, **custom field types** control how data transforms during export operations. Plane's exporter framework uses a class-based architecture where each field type inherits from a base exporter class.

### Creating a Field Exporter Subclass

According to the exporter framework documentation in [`apps/api/plane/utils/exporters/README.md`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/exporters/README.md), all custom field types must extend the **`BaseFieldExporter`** class. This base class provides the foundation for processing work-item data into export-ready formats.

To implement a custom field type, create a subclass that defines a static `FIELD` property and optionally implements a `prepare_<field_name>` method:

```typescript
import { BaseFieldExporter } from "@plane/exporters";

class PriorityExporter extends BaseFieldExporter {
  static FIELD = "priority";

  // Transform raw values before export
  prepare_priority(issue) {
    const priorityMap = { high: 3, medium: 2, low: 1 };
    return priorityMap[issue.custom_properties?.priority] ?? null;
  }
}

```

The `prepare_<field_name>` method receives the issue object and returns the processed value. This hook enables data transformation—such as mapping string priorities to numeric values—before the export finalizes.

### Registering Custom Field Types

After defining your exporter class, you must register it in the **`FIELD_TYPES`** map so the exporter can resolve it by name:

```typescript
FIELD_TYPES[PriorityExporter.FIELD] = PriorityExporter;

```

This registration step connects the field name used in export definitions to the specific processing logic in your subclass. Without this registration, the exporter framework cannot locate your custom implementation when processing export requests.

## Complete Implementation Workflow

Combining custom properties and field types creates a flexible extension system. The following TypeScript example demonstrates the complete workflow from property definition to export:

```typescript
// 1. Define custom property key (used in filters)
const customPropKey = "customproperty_risk_level";

// 2. Store value when creating issue
await fetch("/api/issues/", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    title: "Database migration",
    custom_properties: { risk_level: "critical" }
  })
});

// 3. Filter issues using the custom property
const filter = workItemFiltersAdapter.toInternal({
  and: [
    { "customproperty_risk_level__eq": "critical" },
    { "state__eq": "backlog" }
  ]
});
// Generates ConditionNode tree for the query

// 4. Define custom exporter for risk levels
import { BaseFieldExporter } from "@plane/exporters";

class RiskLevelExporter extends BaseFieldExporter {
  static FIELD = "risk_level";
  
  prepare_risk_level(issue) {
    const severity = { critical: 1, high: 2, medium: 3, low: 4 };
    return severity[issue.custom_properties?.risk_level] || 0;
  }
}

// Register the field type
FIELD_TYPES["risk_level"] = RiskLevelExporter;

```

This workflow demonstrates how the `customproperty_` prefix bridges the UI filtering layer with the backend storage, while the exporter framework handles data transformation for reporting.

## Summary

- **Custom properties** use the `customproperty_` prefix to integrate with Plane's filter validation system in [`packages/shared-state/src/store/work-item-filters/adapter.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/store/work-item-filters/adapter.ts).
- Values store in the `custom_properties` JSON field on the issue model, requiring no schema migrations.
- **Custom field types** extend `BaseFieldExporter` from [`apps/api/plane/utils/exporters/README.md`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/exporters/README.md) to control export formatting.
- Implement `prepare_<field_name>` methods to transform data before export, then register the class in `FIELD_TYPES`.
- The `_isValidWorkItemFilterConditionKey` method validates custom property keys during filter parsing.

## Frequently Asked Questions

### What is the customproperty_ prefix used for in Plane?

The `customproperty_` prefix signals to Plane's WorkItemFiltersAdapter that a filter key represents a custom rather than built-in field. When you include this prefix in filter conditions—such as `customproperty_status__eq=review`—the adapter in [`packages/shared-state/src/store/work-item-filters/adapter.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/store/work-item-filters/adapter.ts) validates the key and converts it into a query condition node. This convention allows the system to distinguish between standard work-item attributes and user-defined custom properties.

### How does Plane validate custom property filters?

Plane validates custom property filters through the `_isValidWorkItemFilterConditionKey` method in the WorkItemFiltersAdapter. This method checks if the filter key starts with `customproperty_`, then parses the property name and operator (like `__eq` or `__contains`). If the key passes validation, the adapter creates a condition node that the backend can translate into database queries against the `custom_properties` JSON field.

### Can I create custom field types for data exports?

Yes, Plane supports custom field types through its exporter framework documented in [`apps/api/plane/utils/exporters/README.md`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/exporters/README.md). You create a subclass of `BaseFieldExporter`, implement a `prepare_<field_name>` method for data transformation, and register the class in the `FIELD_TYPES` registry. This architecture lets you define specialized formatting logic—such as converting string values to numeric codes—when exporting work-item data to CSV or other formats.

### Where are custom property values stored in the database?

Custom property values reside in the `custom_properties` JSON field on the issue model. This Django JSONField stores key-value pairs as JSON, allowing you to add custom properties without altering the database schema. The backend queries this field when processing filter conditions that use the `customproperty_` prefix, making the data available for both display and reporting throughout the application.