Skip to main content

Release of version 1.0.0

Dark mode

Exciting times for our project!

After more than 60 releases, and numerous refinements of the internal components and APIs, we are ready to start the beta release of version 1.0.0 ๐Ÿ’ƒ

We now consider that the code and the UX and UI are mature enough to create a beta release (though we have multiple instances of previous versions already in production!).

In the last months we've been focused on improving the CMS quality by doing a lot of internal restructuring that is also going to affect the public facing APIs.

A bit of background#

We started building FireCMS out of necessity. We were working on several projects based on Firebase and in most of them we needed to develop some kind of backend admin panel/CMS. We took some of those tools we had built, and merged them into a single generic project that would support any database structure and data type. We needed to make sure that we would need to be able to customize many aspects of each implementation based on the necessity.

The outcome was a very good starting point to what would become FireCMS after many iterations and feature updates. One of the consequences of this approach was that the code was tightly coupled to Firebase, and you could find imports to Firebase code in most layers of the code.

The influence of Firebase#

The original coupling to Firebase and specially the data structure of Firestore has influenced many aspects of this project. The way we create entities based on documents and collections and subcollections mimics the Firestore data structure. What seemed like a limitation has turned out to be an opportunity to think of a great UI.

At Camberi we have a special passion for user experience and believe that internal admin tools don't always receive the love they deserve.

In this time, we have developed some high quality components such as the spreadsheet views of collections, or the side panel to edit entities and nested subcollections.

Where we are going#

In the last year, we have seen developers integrate FireCMS in ways we had never imagined! We have tweaked the APIs to try to accommodate those requirements, but we have come to the conclusion that a bigger change was needed.

The result is a big rework of the internal APIs and components that improves re-usability and customisation.

We have also added a lot of minor performance tweaks as well as design tweaks and improvements, including a very awesome new dark mode!

If you are using FirebaseCMSApp (previously CMSApp), so FireCMS as a standalone app, you will need to update some APIs (see the Migrating from alpha versions).

But, but, but... If you need more customisation you can now completely override FirebaseCMSApp and reuse all its internal components. This means you now have control of the MUI theme (adapted to V5), the CMS routes (adapted to React Router 6), or how you display the different parts, such as the side dialogs, or add your own.

You are now free to integrate the new FireCMS component and reuse all the internal components, combine them with your own, build your own custom themes or custom backend implementations.

Separation of concerns#

In the process of having a cleaner code, all the code related to Firebase has been isolated into 3 components, one for authentication, one for the data source and one for storage. These components are abstracted away behind their respective interfaces. This means you can replace any of those parts with your custom implementation!

We have three new types that abstract away the services provided by Firebase:

  • AuthDelegate, in charge of handling the auth operation such as login or sign out, and build the logged-in user.
  • DataSource, in charge of handling data operation (fetch, listen, save, delete).
  • StorageSource, in charge of dealing with file storage operations.

We provide implementation for using Firebase Auth, Firestore and Firebase Storage, assigned by default if you use FirebaseCMSApp.

This means that you are now able to use FireCMS with any backend, auth or storage solution. If you want to go this way, you can check an example implementation of FireCMS that uses the internal components instead of FirebaseCMSApp

How did it go?

If you have been using FireCMS with a custom backend, we would love to hear your feedback either in https://www.reddit.com/r/firecms/ or directly at hello@camberi.com ๐Ÿ˜Š

Code quality#

We have done a huge internal refactor that affects pretty much every part of the CMS. We have now a much better separation of the internal packages. There is also a package named firebase_app which encapsulates an implementation of FireCMS that uses the Firebase services as backend.

All the code referring to Firebase can be found only in this internal package, and nowhere else. This means the core of FireCMS has been completely isolated from Firebase and is now much more maintainable and extendable.

We have got a lot of PRs and support from out users and would love to foster that participation going forward. A cleaner code will help everyone involved and allow for more and better features!

Big types refactor

Types update#

We have a big update in version 0.49.0 affecting the main types used in the CMS.

The signature of EntitySchema<Key extends string = string> has changed to EntitySchema<M> where M is your model type like:

import { buildSchema } from "@camberi/firecms";
type Product = {
name: string;
price:number;
}
const productSchema = buildSchema<Product>({
// ...
properties:{
name: {
dataType: "string",
// ...
},
// ...
}
// ...
});

This change propagates to all the rest of the types used internally and in the public API, especially to all the types that had EntitySchema as a generic type argument.

For example EntityValues (representing the data related to an entity) had the signature EntityValues<S extends EntitySchema<Key>, Key extends string> and now it is simply EntityValues<M>. You may use EntityValues or any other type that includes a schema as a type if you have callbacks in place or have implemented custom views.

This affects in an equal way all the types that are exported. In order to migrate to the new version you need to make sure that you don't have any references like EntityCollection<typeof productSchema>, like the ones we had in the previous examples. In that case you can create your Product type if you want better type checking or simply remove it.

Motivation#

Let's face it, the type system related to schemas until now was a mess.

It had become overcomplicated and was not providing enough type information in the callbacks. Not it is much straightforward.

Robustness#

I have always found it annoying all the Javascript frameworks that use the module.exports system for configuration. I find myself going always back and fort from the IDE to their website to get the information I need, even if I have an idea.

Configuration as code with a strong type system is a whole other level.

The new system is now able to tell you exactly what you are configuring wrong, or suggest the props you are looking for. The following is a configuration error because we are assigning the multiline config, which applies only to strings, to a number property:

// ...
name: {
dataType: "number",
title: "Name",
config: {
multiline: true
},
validation: { required: true }
}
// ...

So it indicates a type error:

Configuration error

Migration examples#

For example, if you had a callback including EntityValues, you would know the property keys you had defined, but not the types.

import { buildSchema } from "@camberi/firecms";
type Product = {
name: string;
price: number;
}
export const productSchema = buildSchema<Product>({
name: "Product",
onPreSave: ({
schema,
path,
id,
values,
status
}) => {
// Now values is of type `Product`
console.log(values);
return values;
},
properties: {
name: {
dataType: "string",
title: "Name"
},
price: {
dataType: "number",
title: "Price"
}
}
});

With this update you get a complete type system in all your callbacks, which will help prevent errors.

Use without specifying the type#

There is a way to get the same type validation without indicating the type explicitly. You can wrap each property with buildProperty to get the same result

import { buildSchema, buildProperty } from "@camberi/firecms";
export const productSchema = buildSchema({
name: "Product",
onPreSave: ({
schema,
path,
id,
values,
status
}) => {
// Now values is of type `{ name: string; price: number; }`, so
// equivalent to the previous example
console.log(values);
return values;
},
properties: {
name: buildProperty({
dataType: "string",
title: "Name"
}),
price: buildProperty({
dataType: "number",
title: "Price"
})
}
});

This way of building your schemas is not encouraged, but it may be useful for simplifying your migration.

In case you need to retrieve the type of the schema you have created, we now include a utility type called InferSchemaType that could be useful if you had code like buildCollection<typeof productSchema>, which now would simply turn into buildCollection<InferSchemaType<typeof productSchema>>

We know that it can be a bit frustrating to migrate code to a newer version and fix breaking changes, but we are convinced that this is a change for the better, making our life and our users easier ๐Ÿ˜‰

Affected types and methods#

All the following types and methods have seen their types changed from <S extends EntitySchema<Key>, Key extends string> to <M>

  • EntityCollection
  • AdditionalColumnDelegate
  • PermissionsBuilder
  • ExtraActionsParams
  • FilterValues
  • EntityCustomView
  • EntityCustomViewParams
  • EntitySaveProps
  • EntityDeleteProps
  • Entity
  • EntityValues
  • FieldProps
  • FormContext
  • CMSFormFieldProps
  • Properties
  • PropertyBuilderProps
  • PropertyBuilder
  • PropertyOrBuilder
  • listenCollection
  • fetchCollection
  • fetchEntity
  • listenEntity
  • listenEntityFromRef
  • createEntityFromSchema
  • saveEntity

And some other components that are only used in advaced use cases.

Hello! Hola!

Welcome to the FireCMS blog!#

We will use this blog to keep you up to date of the latest updates regarding FireCMS, as well as case-studies!

But first, a quick introduction on how we ended up building our own generic headless admin panel/CMS solution based on Firebase!

We started Camberi as an apps and web development agency in 2018. We all were experienced Firebase users, and we found it was a great fit to start new projects, requested by clients.

Motivation#

Usually clients would like to develop an app or a webapp, and most times they would need an additional tool for backend administration. We built 3 tools for different projects, that were similar but slightly different. They were all React projects. We soon realized a generic solution would help us a lot, so we thought: There must be a generic headless CMS based on Firebase/Firestore right?

Right! There were some solutions out there, but we found problems with all of them, which stopped us from using them:

  • Either they were generic CMS tools that were not optimised for the Firestore data structure, with its pros and cons (such as React Admin, despite being a great tool).
  • Or they impose a specific Firestore data structure, which means that are not suitable for every project. This is the case for Flamelink, which is a commercial solution based on Firebase, which follows a philosophy more similar to Wordpress.
  • Or they don't provide enough features. The best solution we have found is Firetable, which shares some core concepts of FireCMS, but we find a bit limited in some Firestore specific areas. It is opinionated on a few things, like the way users are stored for example, and we don't like that. They provide a lot of good widgets but not the possibility to use your own easily.

With all the previous factors taken into account, and a pandemic keeping us home, we decided to unify all the tools we had built into a generic one!

Companies put a lot of effort into building their client facing apps but internal admin tools tend to be the ugly ducklings, with huge productivity implications.

Core concepts#

Pretty soon we knew what we wanted to build. Firestore is a no-SQL database with a data structure that consists of collections, that contain documents, that can contain subcollections, and so on. We need that our UI reflects that structure, with an arbitrary level of nested collections.

It is also crucial that that beautiful UI is customizable, and we have added multiple customisation points based on both our needs and your input.

Our headless CMS must allow developers to extend it in any way they need, while keeping it extremely simple to kickstart a new project, with the bare minimum configuration. We need sensible defaults that can be overridden or extended.

Features#

FireCMS allows setting up a CMS in no time, just by introducing your collections and document schemas as input, and it renders beautiful editable spreadsheet-like tables and forms. It also builds all the navigation and authorization views automatically, so you don't have to. All the common use cases are covered out of the box, including strings, numbers, booleans, arrays, maps, references to other documents, timestamps... We also provide more advanced use cases, like:

  • String enumerations with customization.
  • Handling of files such as images, videos or anything really in Google Cloud Storage.
  • We automatically generate complex fields for references to other collections, just by providing the collection path. Also, we handle correctly arrays of references.
  • Markdown fields, with preview
  • Complex typed array structures: such as an array that contains objects of type text, or images, or products like in our blog example!

Again, besides the basic and advanced use cases that are bundled with the library, the CMS allows the developer to customise and override most aspects of it!

Take a look at the docs if you would like to know all the possible options!

We hope you enjoy using FireCMS as much as we do while developing it, and I would like to take the change to thank all the external contributors with feedback and PRs.

You rock! ๐Ÿ™Œ