# When Customization Becomes Complexity: My Experience Exploring Twenty CRM

There is no shortage of CRM applications. My difficulty was finding one that could be customized to work the way I wanted, rather than requiring me to adapt my requirements around an existing product.

I started researching open-source CRM platforms because building an entire application from scratch seemed unnecessary. Many offered useful functionality, but the interfaces were not particularly appealing to me. I wanted something modern, reasonably structured, and flexible enough to accommodate a different data model.

That's how I came across **Twenty CRM**.

Twenty had several things going for it: a clean interface, an established CRM foundation, active open-source development, and a metadata-driven architecture. The last one was particularly interesting because I wanted to explore whether I could provision customized workspaces and data structures without repeatedly modifying the application at every layer.

The initial experience was encouraging. But after working with its metadata services, extending workspace provisioning, managing upstream changes, and running the development environment, I began questioning the maintenance cost of such a substantial foundation.

Here is what I actually experimented with and what I learned.

## 1\. Understanding Twenty's Metadata Architecture

Twenty's metadata architecture was one of the main reasons I selected it.

In a conventional application, introducing a new entity often involves creating database tables, implementing backend operations, exposing APIs, and building frontend interfaces. Metadata-driven platforms reduce some of that repetitive work by allowing the application to interpret configurable object and field definitions.

Twenty provides metadata services for managing objects, fields, and relationships. Exploring its backend source helped me understand that these configurations are not merely visual settings. They interact with application services and the underlying workspace data model.

For example, Twenty's TypeScript backend uses schema validation to define the information required when creating object metadata. An object has properties such as singular and plural names, labels, descriptions, and other configuration attributes.

But defining an object is only part of the process. The application must also maintain the relationship between its metadata, generated structures, and database state.

My first experiments involved understanding these layers, creating custom structures, and examining how they could be initialized consistently across workspaces.

## 2\. Moving from Manual Configuration to Workspace Provisioning

Configuring one workspace through an interface is manageable. Repeating the same configuration across multiple workspaces is less attractive.

I wanted to understand how custom objects and fields could be provisioned automatically rather than manually recreated every time a workspace was initialized.

This took me into Twenty's NestJS backend, particularly its object and field metadata services. I experimented with a custom provisioning service that operated within a workspace context.

The process involved locating existing objects, checking whether expected metadata was present, and applying changes through the platform's metadata services.

One particularly interesting experiment involved adapting an existing standard object's field configuration without replacing the underlying object or field identity.

Consider a generic CRM where an existing Contact object has a relationship with an Organization. A specialized application might need to present that relationship differently while retaining the original structure.

The following simplified example illustrates how a provisioning service can use metadata APIs to initialize CRM objects and fields within a workspace.

![](https://cdn.hashnode.com/uploads/covers/6a64e4d4959a4ba2ae5f1689/932b1682-13c0-48d0-9dbe-532fa5a031d2.png align="center")

*Simplified illustration of metadata-driven workspace provisioning using TypeScript and NestJS. This is a conceptual example, not the original implementation.*

The important part is not the label change. It is the sequence: locate the existing metadata, verify the expected structure, and update it without unnecessarily replacing its identity.

This approach can reduce structural disruption, but it also creates a dependency on assumptions about the upstream data model. If a future version modifies the underlying object, field, or relationship behavior, the customization needs to be reviewed.

I also began considering idempotency: could the provisioning process be executed repeatedly without creating duplicate objects or unexpected changes?

Some operations used existence checks before proceeding. However, that alone doesn't make an entire provisioning service idempotent. Every create, update, and relationship operation needs to be evaluated.

**Metadata simplifies configuration, but repeatable provisioning still requires careful engineering.**

## 3\. The Upstream Maintenance Problem

About a week into development, upstream maintenance became a much bigger concern than I had anticipated.

Twenty is actively developed, which is normally an advantage. New commits bring improvements, bug fixes, and changes that may be valuable to anyone building on the platform.

But once I began maintaining custom source-code changes, upstream updates required additional attention.

I established a Git workflow that separated the upstream foundation from the customization work. The general process involved fetching changes, reviewing them, updating the base, and then incorporating those updates into the customized branch.

For example:

```bash
git fetch upstream

git log main..upstream/main --oneline

git checkout main
git merge upstream/main

git checkout customization
git merge main
```

This is only an illustrative Git sequence; it assumes the relevant branches and remotes have already been configured.

The commands themselves are not particularly difficult. The real work begins after integration.

Did the upstream changes modify a service I had customized? Were new dependencies introduced? Did database migrations need to run? Would previously provisioned workspaces remain compatible? Did the resulting application still behave correctly?

A successful Git merge does not automatically mean a successful application upgrade.

I also encountered an unexpected side effect when bringing the upstream source into a separate repository: its GitHub Actions workflows came along.

Several automated workflows were designed around the original project's infrastructure, secrets, or development processes. They were not necessarily suitable for my environment, but GitHub was perfectly willing to attempt running them anyway.

That introduced unnecessary jobs, failures, and notifications.

It was a practical reminder that adopting an open-source repository means evaluating more than its application source. CI/CD workflows, dependency management, build configuration, and deployment assumptions also need attention.

You may need the upstream product's improvements without needing its entire engineering infrastructure.

## 4\. Metadata Versioning and Database Compatibility

As I investigated the metadata architecture further, I realized that metadata itself has a lifecycle.

A custom object or field definition is not independent of the application version that interprets it.

Changes to the metadata engine can affect validation, generated fields, relationships, indexes, or migration behavior. Existing database structures may also need transformation when the application evolves.

While examining Twenty's migration architecture, I encountered the distinction between different metadata migration approaches, including mechanisms that expand metadata operations to include system-managed structures.

That raised an important compatibility question: what happens when metadata created under one version is processed by migration logic introduced in another?

For instance, an operation that appears to define a single field may interact with additional generated metadata. If a migration already accounts for those structures, applying a newer generation mechanism without considering the original migration behavior can create conflicts.

This was an architectural finding from source and migration investigation, not evidence that every possible conflict occurred in my deployment.

The distinction matters because creating a fresh workspace and upgrading an existing one are fundamentally different operations.

A fresh workspace can be initialized against a known baseline. An existing workspace may contain records, relationships, custom fields, and metadata created under previous versions.

During experimentation, I also encountered a workspace provisioning issue where the workspace record existed, but the expected database schema had not been fully initialized.

It reinforced a basic but important point: application-level success and database-level consistency must be verified separately.

For a metadata-driven application, upgrade testing needs to examine both the application's source-code compatibility and the state of its existing workspaces.

## 5\. The Operational Cost of a Large Foundation

Beyond metadata and upstream compatibility, I encountered a more immediate problem: resource consumption.

Twenty is a substantial application with many features intended for a broad CRM audience. That is useful when those features match your requirements, but less useful when you need a smaller, more focused application.

I increasingly found myself navigating functionality I didn't require.

Even a three-dot action menu could present a surprisingly long list of options. I wanted a few relevant actions. The application seemed prepared to manage an entire universe.

I appreciate ambition in software, but I wasn't planning intergalactic operations.

The operational demands were harder to laugh about.

During my development experiments, RAM utilization on a machine with 16 GB memory reached approximately 98%, while CPU utilization frequently reached 96–100%. I also encountered build-related troubleshooting and had to consider the infrastructure required to run the application.

These observations are specific to my development setup. They are not controlled benchmarks or evidence of Twenty's general hardware requirements. Container configurations, development processes, workload, and other environmental factors can substantially affect resource consumption.

Nevertheless, the impact on my workflow was significant.

I was spending time investigating builds, monitoring resources, and managing infrastructure instead of developing the CRM functionality I had originally set out to build.

This made me consider the difference between the initial cost of development and the ongoing cost of ownership.

A framework might save months of initial development while introducing recurring work in upgrades, deployment, infrastructure management, and compatibility testing.

Neither cost should be evaluated in isolation.

## 6\. When Customization Starts Competing With Product Development

My experiments with Twenty were valuable because they forced me to examine the boundary between configuration and application ownership.

At the metadata level, Twenty offered considerable flexibility. But once requirements involved application behavior, access boundaries, custom provisioning, and long-term compatibility, the work increasingly extended into the underlying platform.

At the same time, the existing application contained far more functionality than I needed. Removing or working around unnecessary features introduced another category of maintenance.

Eventually, the balance began shifting.

Instead of spending most of my time building the CRM, I was spending an increasing amount of it understanding and maintaining the foundation.

That doesn't make Twenty a poor platform. Its architecture and feature set may be entirely appropriate for organizations that need a comprehensive CRM and are prepared to operate and extend it.

My requirements were simply narrower.

The experience changed how I evaluate open-source foundations. I now consider not only how quickly I can customize a platform, but how much of its architecture I will need to understand, how upgrades will affect my changes, and whether its operational requirements are proportionate to the application I actually want.

A large existing codebase can accelerate development, but it also represents functionality, dependencies, and maintenance responsibilities that come with the decision to adopt it.

**The question is not merely how much code an open-source platform saves you from writing. It is how much of that platform you become responsible for maintaining.**

I still find metadata-driven architecture interesting, and I appreciate what Twenty provides. But my experiments made one thing clear: flexibility is only one part of choosing a software foundation. Compatibility, operational cost, and architectural fit matter just as much.

After all, the objective was to build a CRM, not acquire a second full-time occupation maintaining the universe around it.
