DEV Community

Shirajul Islam Shakur
Shirajul Islam Shakur

Posted on Originally published at shakurshirajul.com

What Shipping a WordPress CRM Taught Me About Being a Developer

Most advice about becoming a developer stops at the moment you get hired. That is the part I found least useful. The gap that actually mattered for me was the one between finishing tutorials and shipping software other people depend on — and nothing I had read prepared me for what that gap contained.

This is what I learned crossing it, building Bit CRM, a sales CRM plugin published on the WordPress.org directory.

Where it started

My father brought a desktop computer home for work when I was three or four. It came with Road Rash, Virtua Cop, and Need for Speed II. I played them constantly, but the machine itself held my attention longer than the games did. I watched what my father did and copied it — media players, Microsoft Word, Excel learning entirely by trial and error. That habit of poking at something unfamiliar until it makes sense is the one thing that has carried through every stage since.

Studying Computer Science and Engineering at IIUC gave that curiosity structure. I learned C and C++, then spent a long stretch on competitive programming on Codeforces and Beecrowd. I want to be precise about what that did and did not teach me, because it is commonly oversold: it did not teach me to build software. It taught me to sit with a problem I could not immediately solve without becoming frustrated, and to break it into pieces small enough to reason about. In production work, that turns out to matter more than knowing any particular algorithm.

The part tutorials do not cover

I learned HTML, CSS, JavaScript, React, then Node, Express, and MongoDB — roughly the standard path. Every tutorial builds its app on an empty page, in an environment that belongs entirely to you.

Real work is almost never like that. When I joined Bit Code and started on Bit CRM, the job was to build a React application inside the WordPress admin — a host environment nobody on my team controlled. WordPress ships its own global styles. Other plugins enqueue their own scripts. The admin shell assumes it owns the page. None of that is a bug; it is simply the environment, and your application is the guest.

That reframes the work. You stop asking "what is the cleanest way to build this?" and start asking "what is the cleanest way to build this here?" Those are different questions, and only the second one ships.

Fitting into conventions you did not choose

Bit CRM integrates with Bit Form, Bit Flows, and Bit Integrations — plugins that were already shipping and already had users before I wrote a line. That meant the CRM had to match the data flow those plugins established rather than impose its own.

My instinct as a newer developer was to treat that as a constraint to work around. It is not. A convention that already works, that other engineers already understand, and that existing users already depend on carries real value and the cost of breaking it is paid by people who never agreed to the change. Learning to read an existing system carefully enough to extend it, rather than rebuilding it in the shape I would have chosen, was probably the largest single shift in how I work.

State is where complexity actually accumulates

I built the plugin dashboard, the client portal, the invoice module, the calendar view, and the activity system covering tasks, meetings, and calls. Individually, none of those is difficult. The difficulty is what happens when you have all of them at once.

What kept it manageable was a strict separation: server state in TanStack Query, local UI state in Zustand. Anything that lives in the database deals, invoices, contacts, activities — is owned by TanStack Query, which handles caching, refetching, and staleness. Anything that exists only in the browser which modal is open, which filter is applied, what is typed into a form — lives in Zustand.

The rule sounds obvious written down. It is easy to violate when you are moving fast and it would be convenient to copy a server value into local state "just for now." Every time that happens you have created two sources of truth that will eventually disagree, and the bug it produces will surface somewhere unrelated, weeks later. Holding the line on that separation is what let the module count grow without the interface becoming unpredictable.

Building for something other than a human

The most unusual part of the project was contributing to the MCP server and the built-in AI assistant, which let the CRM be driven from tools like Claude and ChatGPT.

Designing an interface for a model is genuinely different from designing one for a person. A person tolerates ambiguity — they read a label, look at surrounding context, and infer what you meant. A model has the tool definition and nothing else. Names have to say exactly what a thing does, arguments have to be unambiguous, and errors have to explain what went wrong in terms of what to do next, because there is no user to intuit around a bad message. It made me a more careful API designer than any amount of front-end work had.

What I would tell myself two years ago

Build things that have users. Not portfolio pieces — things where someone is inconvenienced if you get it wrong. Everything that has actually made me better as a developer came from that pressure: caring about maintainability because I will be the one maintaining it, caring about deployment because a broken release is visible, caring about clear naming because someone else has to read it.

You cannot simulate that with a tutorial project, and you do not need to be senior to get access to it. You need one real thing, and then the patience to keep working on it after the interesting part is over. That is where the learning is.

The tools I use now — React, Next.js, TypeScript, Node, Laravel, MongoDB, MySQL — are less important than that, and they will change. Some of them already have. You can see what I am building on my projects page.


Originally published at shakurshirajul.com.

Top comments (0)