Why I won't upgrade to Cockpit CMS v2 and might leave the project

When I started to use and to explore Cockpit CMS (v1) in 2018, I really loved it for its simplicity, modularity and performance. A few things might have been too simple, but there always was an easy way to change or add behavior.

Back then I didn’t care enough about accessibility. If I had, I probably wouldn’t have started using it.

Cockpit CMS v2 is less configurable (or hackable) than v1, there is no addon eco system (yet), a new community needs to grow and it has the same (and some new) accessibility issues as v1.

My main concerns about v2 are:

  1. very bad accessibility
  2. handling of (partial) translated data
  3. hard coded _state
  4. spaces
  5. code and folder structure

In the next sections I’ll go into more details. Some issues are interconnected.



  • fragile architecture with Multiton pattern
  • no real separation from parent addons/modules
  • urls must start with : - looks ugly, probably only useful with a reverse proxy
  • V1 worked with zero variables and only one function (cockpit()) in the global scope. Now $GLOBALS['APP'] is necessary for async calls, which would otherwise break the custom error handler when using spaces. The global variable is also necessary for three helper functions in the System module, which might break otherwise when using spaces.

To be honest - I don’t understand, why I would want to use spaces. I can change storage paths and custom addons folders via config.php on my own conditions (e. g. multiple domains, first url segment, port).

error handling

  • custom error handler eats instant feedback while developing - I always have to press F5 in an sqlite viewer application to read the last error
  • some errors are too big to be written into database, e. g. because of Lime\App as constructor argument, so they don’t even show up in the custom error log table (they do, but as a null value)
  • it doesn’t make sense to run the custom error handler for cli usage
  • also the constant APP_ADMIN occurs only once in the whole source code: to activate the custom error handler

state handling

V1 had no state handling, which was fine. I normally used a boolean field published for custom state handling. Others used a text/select field to handle “published” and “draft” states.

Now three states are implemented:

  • -1: archived
  • 0: not published
  • 1: published

Adding more or other custom states is not really possible.

I’m thinking about using the state functionality for custom states like the ones from Publ:

# source (2023-07-05): https://github.com/PlaidWeb/Publ/blob/main/publ/model.py#L28

""" The status of the entry """
DRAFT = 0       # Entry should not be rendered

HIDDEN = 1      # Entry should be shown via direct link, but not shown on a view
UNLISTED = 1    # Synonym for HIDDEN

PUBLISHED = 2   # Entry is visible

SCHEDULED = 3   # Entry will be visible in the future

GONE = 4        # Entry is gone, won't be coming back
DELETED = 4     # synonym for GONE

ILLEGAL = 5     # taken down for legal reasons (error code 451)
DMCA = 5

TEAPOT = 6      # RFC 2324

A workaround might be to set the _state of all items to published by default, to add a custom field and use the event system to handle custom states. Because states are a core feature, they also must be disabled/hidden in multiple places in the admin ui now if I want to bypass it completely. This sounds fragile, because future updates might break these changes.

localized content

It feels like localisation is just a nice-to-have on top of single language data. Especially partial translated data can’t be handled well.

  • required attribute only cares about the default language
  • core _state handling is combined for all languages - publishing the default language now and translating later is not possible
  • Using the core rest api endpoints applies language processing by default. This is problematic for multiple reasons:
    • If some fields aren’t translated, the default language is used as a fallback. The information, that the language is different is missing, so I can’t write proper html markup with the right lang attribute for these data points.
    • In v1 I was able to get all translations with an api call and doing comparisons about partial translated data later. Now I have to write a custom api endpoint to get all translations at once.

localized web ui

The i18n handling should have namespaces/annotations to care about similar written words with different meanings (verb/noun, per module). This was also a problem in v1.


In short: It sucks.

It feels like a nice-to-have, that might be implemented later. Especially with the EU Web Accessibility Action Plan (German: Barrierefreiheitsstärkungsgesetz), this topic should get a higher priority very soon.

Some examples:

  • fixed/sticky elements prevent keyboard navigation (form fields are hidden behind action bar or header)
  • many ui elements aren’t accessible via keyboard at all
  • font size too small
  • bad contrast (at least in dark mode with white text on blue)
  • I can’t remember the meaning of the icons in left side bar. So I always have to hover with my mouse over the icon to see a tooltip. Expanding the menu to actually read text only works on small viewports.
  • many a elements should be buttons
  • also some divs should be a or button
  • <kiss-navlist> should be <nav>
  • icon links need aria-hidden
  • over-use of aria-label - use semantic html instead

defining custom paths/folder patterns

Using custom paths is useful to e. g. move the storage and the config folder above the webroot for further security. Maybe the .htaccess files is not copied after restoring a backup (these damn dot files and shell expansion). Maybe some server config sucks and suddenly the .htaccess is ignored and php files are printed as text/plain (more a “change crap web hoster immediately” problem, but I saw these things a few times).

In v1 we had a defines.php to set constants with custom file and folder paths. If I want to use custom paths now I have to write a custom index.php, instantiate Cockpit with custom config and add a modified Async helper class to reflect the custom config. This feels very fragile.

hiding security updates in “cleanup” commit messages

This one got much better in v2 than in v1 with the introduction of CHANGELOG.md. I actually read (or skim) all commits and it would be nice to be able to skip the ones, that are just named “cleanup”. Not a deal breaker, but annoying.

ui framework(s)

The php based templating with Lexy in combination with riot.js was quite intuitive from reading the code.

The switch from riot to vue feels like a really good decision. It is very well documented, which makes it easy to understand the source code or to write a new addon.

I never liked UiKit (very bloated), but I could always search for uikit v2 {feature} to get a basic idea of all the fancy css classes and data atrributes.

Now there is kiss, which might translate to “Keep It Short and Simple”. It’s definitely better than UiKit, but undocumented. It’s also not clear, which components are part of kiss, custom components, vue components or some vendor lib inside App or System module. It might be simple, but it is actually hard to use.

no php template engine anymore

  • LimeExtra\App with Lexy from v1 was simple, but powerful enough
  • now I have to use plain php to escape variables or I have to hack into the core controller to overwrite the default render method to pass in a custom renderer (e. g. modified Lexy, Blade or Twig)

Using Lexy in v1 was also the reason, why no helper functions in the global scope were needed (e. g. @lang() instead of t()).

structure of core modules is not intuitive

Everytime I search for a file (e. g. Controller, Helper class or a component file), I have to look through many sub folders in the App and System module folders. I don’t understand, why they are separated. Even in the i18n handling, both modules are merged. It would be much cleaner, to drop the System module and merge it into the App module.

missing features

I’m not generally against paying for good software, but I’m not interested in proprietary software at all. So using the Pro features of Cockpit CMS v2 is not an option. To prevent accidentally violating copyright, I never read the source code of the Pro addons. But I assume, that they have the same accessibility problems like the core.

Also I can’t read the commit history of the Pro addons.

If I decide to use v2, I’ll probably reimplement the forms module from v1 with additions from my FormValidation addon. Because I care about accessibility now, I can’t really reuse many core features anymore. So this would be much more work, than I initially thought.

web console

I love the cli. Moving to Symfony\Console was also a good idea. But the web console has copy/paste problems, no shell history and it is exposed without using an SSH key. This is an unnecessary attack vector. I could disable it with overwriting the web console controller. So again - no deal breaker, but annoying.

It should be a separate module/addon, like the Finder (which was hard to disable in v1 - also for security reasons).


  • it never really felt like a community, more like a knowledge base or a FAQ section
  • active users are gone (e. g. @serjoscha87, @pauloamgomes)
  • information is cluttered
    • v1 and v2 related questions on discourse without proper categorization
    • v2 related questions on Github Discussions - This is just another Microsoft dependency. Also I don’t want to be logged in on Github all the time. SSH keys for git usage exist for a reason. I won’t participate much over there.


I’m still a big fan of the Lime micro framework, the event system and the modularity/hackability of Cockpit CMS.

I learned a lot about many core concepts for building a CMS, writing user interfaces and delivering performant frontends during the last years. Also my OOP skills and knowledge about PHP and JS libraries improved massively.

Now I’m not sure, if my main concerns could be addressed in the near future.

I also thought about using Cockpit (v1 or v2) as a library and writing a complete new user interface. In this case I would deactivate all features and implement one by one with progressive enhancement and accessibility in mind. This would be a massive amount of work.

In terms of modularity, accessibility and community Drupal seems to shine. After some research, the event system is much more complicated, it uses SQL, it’s not performance optimized, it still requires jQuery, RAM usage seems quite high (server and client side) and the default footprint is about 100MB (vs. 26MB of CP v2 or 19MB of CP v1). Maybe I get more comfortable with it during the next weeks…

After spending so much time with Cockpit, reading and contributing code, helping in the forum, reporting many security issues in private chats and writing multiple addons, it feels hard and wrong to leave this project. But I don’t feel comfortible with v2 for months now. Instead of spending even more time, it might be better to move forward and to learn about some interesting concepts from other content management systems…

I would be happy about opinions - similar experiences or why it’s still worth believing in Cockpit.

Also suggestions, which other CMS might be worth testing (open source, modular, accessible, PHP or JS based) are appreciated.

1 Like

Please can I respond to the principle behind your response/comment and not to the detail. I have saved the detail of your response, so that I can read and digest it more fully during the next few days and weeks.

I fully appreciate that you have been one of the major assets and contributors to Artur and the Cockpit CMS project. When I used Cockit V1, I was an avid user of some of your addons. I was dearly hoping that you might do the same for V2 and start the ecosystem growing.

As a framework and CMS developer, like Artur, I fully understand where all sides are coming from. I worked on my Cliqon since 2002 and only ceased activity, in principle, because I retired (officially) in 2016. In Spain, where I then lived, I was not allowed to be retired and also run a business. Now I have returned to the UK to live, that is no longer a concern for me - I can work and draw my pension! For a project such as Cockpit or Cliqon, that is an absolute benefit. I don’t have to worry about making a living and can put in as much time as I want to into programming.

Now I have returned to the UK, I immediately became embroiled in three new enduser website projects and needed to produce something very quickly. My goto combination of Cliqon front end and Cockpit backend (whichg I call Cliqonlite) allowed me to solve my immediate problems. BUT at best Cockpit CMS is a great prototyping tool because it is not scalable - it needs a proper SQL server database backend. It could easily have one. Why do I say that? Because what attracted me to Cockpit in the first place was because Artur had programmed the base record with an id and JSON, just as I had done with Cliqon - the only way we differed was in the names of our fields, for example _cby or _mby in Cockpit, where I had used clq_createdby and clq_lastmodified.

However Lime is not a good enough framework, so I use Flight. I see no reason to use Twig or anything like it for templating, so I use a PHP version of Razr. We share use of Vue. I prefer Bootstrap to UIKit.

But none of this relevant to the major point I want to address. Cockpit CMS starts from a point that no other Framework, developed in PHP, currently starts from - design a data form and that creates the data dictionary.

CakePHP, Codeigniter, Slim and even Laravel have all had their CMS and data dictionary creating offsprings - October, Winter and others whose names now escape me. Cockpit has stood the test - why because it works and it is so blindlingly simple in concept. This is why I take my hat off to Artur.

But you want to make things better and perfect. I don’t see how Artur is ever going to do that. My guess is that he reads your submission and says “great ideas, who is going to pay me?” and anyway, do we want that such a development purely belongs to Artur.

To make matters worse and this is not Artur’s fault. His lovely name of Cockpit got highjacked by the popular Linux panel program - a simple man’s Webmin.

No marketeer would recommend using Cockpit as a product name. This is where, I score with my Cliqon. Zoho approached me, years ago, to buy the name and I said no. As a result I have a lovely name but currently, virtually no product.

BUT I have recntly decided that I am going to come out of retirement and work on Cliqon 8. I would really, really like that to be a shared and opensource project, for all times. I do not want that yet another opensource program that gets highjacked by the Indian sub continent and turned into a revenue stream.

I want to see the best product possible, put together by a remote team where everyone earns money, eventually, by rolling out enduser websites, providing paid support and creating sensibly priced extensions.

Raffael, is that not really what you want to achieve?

If I am right, then lets all talk. Agree a specification for a new best of breed product - written for PHP 8, that knocks the socks off Laravel and Umbraco (which ripped me off in 2005 and still does the business in the ASP.NET marketplace - also it keeps my family in money in the UK).

WordPress is such gargantuan crap. I am completely cheesed off that PHP and Laravel have become almost synonymous.

We can do better.

1 Like

Hi Raffael :wave:

I’m sorry to hear that you’re unhappy with the direction Cockpit has taken. But like many things in life, everything has its reason.

But let me briefly address some of your points:

Very bad accessibility

This point seems particularly important to you. I think that’s good and correct. However, accessibility is a vast area and requires deep familiarization. As much as I regret it, it is simply not possible with the current resources.

Of course, I try to address specific feedback as time permits and implement improvements.

Hard coded _state

Yepp, right. However, it is currently the foundation and will be expanded into a workflow system in the future. It is definitely not the final version.

The current implementation allows the frequent requirement to toggle content active or inactive. And it has been standardized since v2.


Spaces are currently still in beta status. However, they already enable many scenarios. You say you see no use for Spaces. Let me show you a few:

  • Staging environments (Dev, Preview, Prod)
  • Test environments
  • Multitenant environments

For many agencies and projects, this is a game-changer (especially in combination with the Sync addon) to quickly create an isolated instance.

Commercial version & Addons

I don’t want to go over the reasons for a commercial Pro version again. Everyone can download the Pro version without any restrictions and test it, and then decide whether it makes sense for them or not. Transparent and fair, I think.

However, I also don’t discourage anyone from creating their own extensions and happily sharing them with others.

You say that the effort to port your existing addons is too high. What’s stopping you from setting a good example and creating an extension where the accessibility doesn’t suck? I’m sure there’s a lot I can learn from that as well.

I think Cockpit is still a very flexible system, but it also has to face the challenges of the times.

It looks like we don’t have the same idea of how to approach these challenges.

If you do choose to move on, your contributions to the Cockpit community will surely be missed.

Keep on growing and do exploration into new territories. Maybe and hopefully in the end we’ll see us again :slightly_smiling_face:

1 Like

Thanks @webcliq and @artur for your feedback. I think, I need to clarify a few things to make my concerns more understandable.

Especially new users might not know about v1 or my previous work and I went deep into technical details.

custom php frontend

I always was more interested in the PHP (and the CLI) api than in the REST api. On top of the headless Cockpit CMS v1 I wrote my own frontend (head) Multiplane. So my usage always was unusual or not like intended, but I tried to write all my addons to be compatible with the headless paradigm. When I started to port Multiplane to v2, I discovered all these issues, I mentioned in my initial post.

Because defining custom paths was dropped in favour of using the Cockpit::instance() pattern, it isn’t compatible anymore to be used as a foundation.


During the last year I read a lot about inclusive web design. Before, I already cared about semantic HTML, which is a great starting point to avoid exclusive web design. So I did many things right without knowing about the impact for disabled people. I also did many things wrong (e. g. error messages after validating a form were undetectable for blind people).

I don’t see myself as a disabled person. Maybe I am. I can’t concentrate on reading a text when a single element in the viewport keeps moving. Also every now and then my typists’s cramp (RSI) comes back. I see myself more as a power user. These things are interconnected. I use the keyboard, because typing is faster, but it also supports my right wrist. I use script and ad blockers for privacy, political and security reasons, but also to be able to read text at all.

Opening the same issues twice thrice, after fixing them in v1 already, annoyed me, e. g.:

I skimmed my feedback post from one year ago again and I found both issues above, as well as most issues mentioned in this thread.

If you want to improve your accessibility skills, I recommend starting with Best practices for inclusive textual websites from Rohan Kumar. I found a good overview about screen readers and form elements from Russ Weakley. He also has some more resources, but I didn’t read them, yet. And I follow a few interesting people on Mastodon so I have daily input about accessible web design in my news feed. It’s a huge topic and it fundamentally changed my view on code.

disabling vs. enabling features

I chose v1 because I was tired of playing whack-a-mole with WordPress by disabling (new) features (mainly third party requests and Gutenberg CSS changes). With Cockpit I still had to disable a few things via my rljUtils addon (including a security patch, that was never fixed in v1 - after I reported it again, it was fixed in v2) but my modifications were stable for years.

To bend Cockpit to my needs, I would have to modify the core, which would lead to a very unstable foundation.

Also I have to disable the user ip logging now, which must be opt-in instead of opt-out.


I totally understand the mentioned use cases, but I don’t like the implementation. All use cases were possible with v1 by writing some code in defines.php, config/bootstrap.php and config/config.php and using a .env file.

Alternatively it was possible to load Cockpit as a library with some quick and dirty js fixes to correct paths in the admin ui (first proof of concept, old variant for Multiplane, latest implementation for Multiplane).

pro addons

It is possible to publish the Github repos without a permissive license.

If I could read the commit history, I could have a better understanding of some decisions about changed core code, that only makes sense e. g. with the Sync addon.

Now I would have to download the latest pro zip archive, extract it and run a manual diff on an addon folder to keep track of changes. This doesn’t feel transparent.

Because I have that deep insights (like the above mentioned security issue, that never got fixed) and because v1 never had a changelog, I lost some trust in published releases. Without reading (or at least skimming) the code, I wouldn’t install them. This also applies to other contexts like WordPress plugins, so it’s not just a Cockpit issue. But making the code open source (with commit history) helps gaining trust.

As I said, I didn’t have a look at the pro addons to avoid copyright problems. My Multiplane implementation would compete with the Pages addon.

And I planned to improve the BlockEditor prototype (probably with prosemirror/TipTap). It was based on the layout field, which is now a pro addon, too.

So this is another field where I can’t build on an existing foundation anymore.

specific answers

  1. If I can’t use v2 as a foundation for Multiplane, I’m not motivated to rewrite my addons.
  2. Porting just the business logic in PHP is the simple part. Writing a user interface is hard and you did a great job with building an intuitive UI in most places. Most times I spend more time on writing the UI and thinking about UX than solving the actual problem with a few lines of PHP.
  3. Accessibility doesn’t work well as an after-thought. If I can’t navigate to the addon settings, it doesn’t help much when the addon is accessible.
  4. In the Babel addon I used an event listener on all text inputs and a CSS fix to address the fixed header/footer issue. Being able to use the Tab key while editing hundreds of form fields was worth the loss in performance and the increased RAM usage. The real fix would be to drop fixed and sticky elements or to go the hard way and build an accessible sticky header and fixed footer (I read somewhere, that it’s actually possible).
  5. If I would start from scratch, I would fundamentally change things. With HTML5 and basic form fields you get most accessibility features for free. Add some CSS with pseudo elements to avoid div soup. Than JS follows as a progressive enhancement. In this regard Cockpit is actually a good base, because Lime is compatible with form data by design and I’m not forced to send json via ajax. So it could work even when JS is disabled. This concept is hard to achieve while developing with JS frameworks (vue, kiss). A PHP based templating engine would help here :wink:. Drupal does these things right. A few days ago I wondered, why CKEditor didn’t load while editing and saving the content field with plain html in a textarea. I didn’t notice, that I disabled Javascript on http://localhost a few days earlier for a different test and it just worked.
  6. I don’t want to start from scratch. Instead of spending years on my ideal CMS I want to build on a strong foundation and improve things over time.

I agree :wink:

I agree, but for a different reason. With MongoDB and cloud storage support, Cockpit should scale well (I never needed scaling, so not tested). If I ignore my mentioned limitations, Cockpit is a great tool for prototyping. It just doesn’t match my needs for production anymore.

Yes. Everytime I see some configuration in another CMS, I miss that simplicity.

Actually no. I think, that Cockpit as a project is too big for one person.

You nailed it. But as mentioned above, I don’t want to start from scratch.

I do already. Right now I’m reading the docs of Neos. They have some great core concepts. I’m not sure about their state of accessibility, but it might be quite good. Unfortunately, it is the opposite of simplicity with a default footprint of 200MB, but they have inline-editing, which is simple for end users.

Yesterday the data or content of my first client website collapsed. That is to say, the front-end still accessed the data but in Cockpit Admin, all I had for Collections was a nice animated GIF and console.log told me a Promise was not returned (Precondition not fulfilled). I had access to singleton but not to collections.

Copying the files from a backup did not solve the problem - I have my suspicions as to what has happened which I am sure Artur will confirm.

The moral of the story is that I am not going to install a server version of MongoDB not can I use a cloud version. I always manage things myself on my own dedicated servers.

So I have looked into JSON on MySQL/MariaDB and see absolutely no problem to replicate the Cockpit data dictionary onto MySQL.

INSERT INTO dbitem (qtype, qdocument, qnotes)
		"qreference", "str(0)",
		"qcommon", "Login",
		"qtext", JSON_ARRAY(
	'Additional notes');

SELECT id, qtype, JSON_EXTRACT(qdocument, '$.qtext') AS qtext, qdocument, qnotes FROM dbitem;

qtext = array

SELECT id, qtype, JSON_EXTRACT(qdocument, '$.qtext.en') AS qtext, qdocument, qnotes FROM dbitem;

qtext = null

SELECT id, qtype, JSON_EXTRACT(qdocument, '$.qtext[en]') AS qtext, qdocument, qnotes FROM dbitem;

qtext = null

SELECT id, qtype, JSON_EXTRACT(qdocument, '$.qtext["en"]') AS qtext, qdocument, qnotes FROM dbitem;

qtext = null

SELECT id, qtype, JSON_EXTRACT(qdocument, '$.qtext[0]["en"]') AS qtext, qdocument, qnotes FROM dbitem;

qtext = null

SELECT id, qtype, 
	JSON_EXTRACT(qdocument, '$.qreference') AS qreference, 
	JSON_EXTRACT(qdocument, '$.qcommon') AS qcommon,  
	JSON_EXTRACT(qdocument, '$.qtext') AS qtext,  
FROM dbitem;

SELECT id, qtype, 
	JSON_EXTRACT(qdocument, '$.qreference') AS qreference, 
	JSON_EXTRACT(qdocument, '$.qcommon') AS qcommon,  
	JSON_EXTRACT(qdocument, '$.qtext') AS qtext,  
FROM dbitem;


SELECT id, qtype, 
	qdocument => '$.qreference' AS qreference, 
	qdocument => '$.qcommon' AS qcommon,  
	qdocument => '$.qtext' AS qtext,  
FROM dbitem;

error - not supported in MariaDB, only MySQL

SELECT id, qtype, 
	JSON_EXTRACT(qdocument, '$.qreference') AS qreference, 
	JSON_EXTRACT(qdocument, '$.qcommon') AS qcommon,  
	JSON_EXTRACT(qdocument, '$["$.qtext"]["$.en"]') AS qtext,  
FROM dbitem;

error - no variety of this works


Do we keep qtext as an array or create separate individual versions?

qtext: ['en':'Login', 'es':'Acceder]


qtext_en = 'Login'
qtext_es = 'Acceder'

I am using my own datadictionary nomenclature but you can see the point.

JSON on MariaDB or MySQL has limitations regarding accessing embedded arrays but so has MongoDBLite, so as to speak, and the problem about the best way to handle multiple languages still applies. However I can see my own way forward but I thought I would share my findings with you all.

1 Like