- Click on the `Create Service Account` button.
- Fill in the details for the service account. Name it `FireCMS`.
- Assign the following roles:
- `Firebase Admin`
- `Firebase Admin SDK Administrator Service Agent`
- `Firebase Service Management Service Agent`
- Optionally, define the users that can impersonate the service account.
- Now let's create the JSON key that will be uploaded to FireCMS Cloud.
Find the newly created Service account, and in the dropdown menu, click on `Manage keys`.
- Then create a new key.
- And finally, download the JSON key.
Now you can **upload this JSON key** to **FireCMS Cloud** and link it to your project.
:::important[Security]
A service account is a special type of Google account that allows non-human users to authenticate and authorize
Google Cloud Platform (GCP) services. It is important to keep the service account key secure, as it can be used to
access your GCP resources. FireCMS Cloud uses this service account to manage your project resources.
Your service account is securely encrypted using Google Cloud KMS.
Make sure to keep your service account key secure and do not share it with unauthorized users.
:::
## Migrating from FireCMS 2.0 to FireCMS Cloud
:::important
This migration guide applies for migrating from FireCMS 2.0 to FireCMS Cloud
:::
FireCMS 3.0 is a major release that introduces a lot of changes. This page
describes the main changes and how to migrate from FireCMS 2.0.
### Create a project in app.firecms.co
FireCMS Cloud requires the creation of a project in [app.firecms.co](https://app.firecms.co).
The new version relies on a backend that allows you to manage your
collections and schemas. The final users are now able to modify collections,
so we use a centralised service to store the configuration.
By doing this you will **not need to specify your Firebase project credentials**,
since the service will be able to access your project directly. You will only need
to specify the **project id**.
### Initialize a FireCMS Cloud project in a new folder
It is advisable to create a new project from scratch and then migrate your collections and views to
the new folder.
In order to do so, run
```
npx create-firecms-app
```
and create a new project in a new folder.
The CLI will initialize an empty project with the new format, and all the configuration files
ready so you don't need to worry about it.
### Migrating collections to the new format
Despite the new format, FireCMS aims allow users to migrate existing apps with minimal
changes. The collections can be now stored both in the FireCMS backend or defined in
code like until now.
Also, you can have collections defined in both places, and decide if the code defined collections
can be modified by the user or not.
Please note that properties defined in code will not be editable by the user, unless you
explicitly mark them as `editable: true`.
### package.json
For reference, the `package.json` file of a new FireCMS Cloud project looks like this:
```json
{
"name": "my-firecms-project",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --port 5001",
"build": "vite build",
"serve": "vite preview --port 5001",
"deploy": "run-s build && firecms deploy --project=your-project-id"
},
"dependencies": {
"@firecms/cloud": "^3.0.0",
"firebase": "^12.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@originjs/vite-plugin-federation": "^1.4.1",
"@tailwindcss/typography": "^0.5.10",
"@types/react": "^18.2.71",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.3",
"vite": "^5.2.6"
}
}
```
### New format
Since it is now possible to deploy FireCMS in our hosted service, the output
of your project needs to be in a specific format.
The `index.ts` file should export a `FireCMSAppConfig` object, which is defined as follows:
```typescript
export type FireCMSAppConfig = {
/**
* Customization schema version.
*/
version: "1";
/**
* List of the mapped collections in the CMS.
* Each entry relates to a collection in the root database.
* Each of the navigation entries in this field
* generates an entry in the main menu.
*/
collections?: EntityCollection[] | EntityCollectionsBuilder;
/**
* Custom additional views created by the developer, added to the main
* navigation.
*/
views?: CMSView[] | CMSViewsBuilder;
/**
* List of custom form fields to be used in the CMS.
* You can use the key to reference the custom field in
* the `propertyConfig` prop of a property in a collection.
*/
propertyConfigs?: PropertyConfig[];
/**
* List of additional custom views for entities.
* You can use the key to reference the custom view in
* the `entityViews` prop of a collection.
*
* You can also define an entity view from the UI.
*/
entityViews?: EntityCustomView[];
}
```
Let's break down the different fields:
### Collection configuration
Collections have suffered minimal changes. If you don't have any custom components defined, it should be
easy to adapt your collections to the new format.
- You need to define an `id` for each collection, which typically can be the same as the `path`.
- The prop `views` has been renamed to `entityViews`, since they are applied to entities.
- For `AdditionalFieldDelegate` the prop `id` has been renamed to `key`.
To migrate your collections, simply export them in your `index.ts` file:
```typescript
const appConfig: FireCMSAppConfig = {
version: "1",
collections: async (props) => {
return ([
productsCollection
]);
},
propertyConfigs: [
colorPropertyConfig
],
entityViews: [{
key: "test",
name: "Test",
Builder: SampleEntityView
}]
}
```
- The `views` property has been renamed to `entityViews`, since they are applied to entities.
- The `path` prop of views has been renamed to `key`, for consistency with the rest of the library.
### Migrating custom components (MUI)
FireCMS 3.0 is based on `tailwindcss` instead of `mui`.
Mui was great for the initial versions of FireCMS, but it was being a big performance bottleneck
and it was hard to customize.
The new version of FireCMS has built in almost 50 new components implemented with tailwindcss, that
mimic in a good way the material-ui components. You are encouraged to migrate your custom components
to the new format.
However, if you want to keep using mui: you can still use the old components, but you will need to
install the `mui` package manually.
```bash
npm install @mui/material @emotion/react @emotion/styled
```
or
```bash
yarn add @mui/material @emotion/react @emotion/styled
```
If you need MUI icons, run:
```bash
npm install @mui/icons-material
```
or
```bash
yarn add @mui/icons-material
```
#### Components that have no equivalent:
- `Box`: The box component is just a wrapper used by mui to apply styles. You can use a `div` instead.
Tip: ChatGPT is great at converting Box components to div with tailwind classes.
- `Link`: Use `a` instead.
- `FormControl`
#### Components that change behaviour:
- `Menu` and `MenuItem`: Menu items do not have an id anymore. You can add an `onClick` props per menu item.
- `Select` does not use `labelId` anymore. Just add the label as a component in `label`.
- `SelectChangeEvent` is now `ChangeEvent.ts` following the
structure of the existing [`en.ts`](https://github.com/firecmsco/firecms/blob/main/packages/firecms_core/src/locales/en.ts).
2. Register it inside
[`FireCMSi18nProvider.tsx`](https://github.com/firecmsco/firecms/blob/main/packages/firecms_core/src/i18n/FireCMSi18nProvider.tsx).
3. Open a pull request — thank you! 🎉
Have questions? Join the [Discord community](https://discord.gg/fxy7xsQm3m).
## Self-Hosted Deployment
FireCMS works as a **headless CMS** on top of Firebase. It builds as a **single page application** that can be deployed
to any static hosting provider. It does not require any server-side code.
We recommend deploying to Firebase Hosting, as it is in the same ecosystem, and FireCMS will even
pick up the Firebase config from the environment.
### Deployment to Firebase Hosting
If you would like to deploy your CMS to Firebase Hosting, you need to enable
it first in the Hosting tab of your Firebase project.
You will need to init Firebase, either with an existing project or a new one:
```
firebase init
```
:::note
You don't need to enable any of the services, besides Firebase Hosting if you
would like to deploy it there.
:::
You can link the Firebase hosting site to the webapp that you have created
in order to get your Firebase config.
In order to make everything work as expected, you need to setup Firebase Hosting
redirects to work as a SPA. Your **firebase.json** should
look similar to this (remember to replace `[YOUR_SITE_HERE]`).
```json5
{
"hosting": {
"site": "[YOUR_SITE_HERE]",
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
```
Then simply run:
```bash
npm run build && firebase deploy --only hosting
```
or
```bash
yarn run build && firebase deploy --only hosting
```
to deploy.
```bash
npm run build
or
```bash
### Deploying to other platforms
If you would like to deploy your CMS to other platforms, you can build it
with:
```
yarn run build
```
and then serve the **dist** folder with your favorite static hosting provider.
## App Check
You can integrate Firebase App Check with your app to protect your backend resources from abuse, such as billing fraud
or phishing. Firebase App Check works alongside other Firebase services, such as Firebase Authentication,
to help secure your backend resources.
FireCMS provides a simple way to integrate Firebase App Check with your app.
:::important
Remember to add the domain where you will be deploying your app to the list of allowed domains in AppCheck provider
configuration.
:::
For self-hosted versions, you can enable Firebase App Check in your app by providing the `options`
and `firebaseApp` props in the `useAppCheck` hook.
The `useAppCheck` hook is used to initialize Firebase App Check and monitor its status.
It handles the asynchronous initialization process, provides loading state, and captures any errors that
may occur during initialization.
#### Parameters
- `firebaseApp` (optional): An instance of `FirebaseApp` to use for App Check initialization.
- `options` (optional): Configuration options for App Check.
- `provider`: The provider you want to use.
- `isTokenAutoRefreshEnabled`: Whether to automatically refresh the token.
- `debugToken`: A debug token to use.
- `forceRefresh`: Whether to force a token refresh.
#### Return Value
Returns an object that includes:
- `loading`: A boolean indicating whether the initialization is in progress.
- `appCheckVerified` (optional): A boolean indicating whether the app has been verified by App Check.
- `error` (optional): Any error encountered during the initialization process.
#### Example
```tsx
const {
loading,
error,
appCheckVerified
} = useAppCheck({
options: {
provider: new ReCaptchaEnterpriseProvider(process.env.VITE_RECAPTCHA_SITE_KEY as string),
isTokenAutoRefreshEnabled: true,
}
});
```
### Providers
The `provider` property is required and should be an instance of a Firebase AppCheck provider.
You can use one of the following providers:
#### ReCaptchaEnterpriseProvider
In order to set up the ReCaptchaEnterpriseProvider, you need to create a new reCAPTCHA Enterprise site key.
Follow the instructions in the [Firebase documentation](https://firebase.google.com/docs/app-check/web/recaptcha-enterprise-provider).
:::important
Make sure you have added the domain `app.firecms.co` to the list of allowed domains in the reCAPTCHA Enterprise console.
:::
```tsx
const {
loading,
error,
appCheckVerified
} = useAppCheck({
options: {
provider: new ReCaptchaEnterpriseProvider("your-site-key"),
isTokenAutoRefreshEnabled: true,
}
});
```
#### ReCaptchaV3Provider
In order to set up the ReCaptchaV3Provider, you need to create a new reCAPTCHA v3 site key.
Follow the instructions in the [Firebase documentation](https://firebase.google.com/docs/app-check/web/recaptcha-provider).
```tsx
const {
loading,
error,
appCheckVerified
} = useAppCheck({
options: {
provider: new ReCaptchaV3Provider("your-site-key")
isTokenAutoRefreshEnabled: true,
}
});
```
#### Custom provider
You can also create a custom provider by implementing the `AppCheckProvider` interface.
## Integrating MongoDB Atlas with FireCMS
This guide details the steps necessary to integrate MongoDB Atlas as both the authentication system and the primary database for your FireCMS application.
Mongo Atlas does not support file storage, so you can optionally use Firebase for this purpose, or any
other storage provider.
### Project Setup
Start by generating a new project using the FireCMS PRO starter template.
```bash
npx create-firecms-app --pro
```
or
```bash
yarn create firecms-app --pro
```
### MongoDB Atlas Configuration
Begin by setting up your MongoDB Atlas project and obtaining the required configuration parameters.
These parameters include `appId`, `appUrl`, `baseUrl`, `clientApiBaseUrl`, `dataApiBaseUrl`, `dataExplorerLink`,
and `dataSourceName`.
```js
const atlasConfig = {
appId: "your-app-id",
appUrl: "https://services.cloud.mongodb.com/groups/your-group-id/apps/your-app-id",
baseUrl: "https://services.cloud.mongodb.com",
clientApiBaseUrl: "https://your-region.gcp.services.cloud.mongodb.com",
dataApiBaseUrl: "https://your-region.gcp.data.mongodb-api.com",
dataExplorerLink: "https://cloud.mongodb.com/links/your-group-id/explorer/Cluster0/database/collection/find",
dataSourceName: "mongodb-atlas"
};
```
### Firebase Configuration (Optional)
If you wish to use Firebase for file storage, configure Firebase as follows:
```js
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-auth-domain",
databaseURL: "your-database-url",
projectId: "your-project-id",
storageBucket: "your-storage-bucket",
messagingSenderId: "your-messaging-sender-id",
appId: "your-app-id"
};
```
### Implementing MongoDB in FireCMS
Let's create a new `MongoDBApp` component that integrates MongoDB Atlas (and optionally Firebase).
#### Initialization
Initialize Firebase and MongoDB within your component:
```jsx
const MongoDBApp = () => {
const name = "My FireCMS App";
// Initialize Firebase
const { firebaseApp, firebaseConfigLoading, configError } = useInitialiseFirebase({ firebaseConfig });
// Initialize MongoDB
const { app } = useInitRealmMongodb(atlasConfig);
// ...
};
export default MongoDBApp;
```
#### Controllers and Persistence
Next, set up the mode controller, user configuration persistence, and MongoDB auth controller.
```jsx
const modeController = useBuildModeController();
const userConfigPersistence = useBuildLocalConfigurationPersistence();
const authController: MongoAuthController = useMongoDBAuthController({ app });
const mongoDataSourceDelegate = useMongoDBDelegate({
app,
cluster: "mongodb-atlas",
database: "todo"
});
```
#### Firebase Storage Source
If you plan to use Firebase for file storage, initialize `storageSource`.
```jsx
const storageSource = useFirebaseStorageSource({ firebaseApp });
```
#### Authenticator Validation
Define the validation logic for your authenticator.
```jsx
const { authLoading, canAccessMainView, notAllowedError } = useValidateAuthenticator({
authController,
authenticator: () => true, // Replace with your logic
dataSourceDelegate: mongoDataSourceDelegate,
storageSource
});
```
#### Navigation Controller
Set up the navigation controller with your collections.
```jsx
const navigationController = useBuildNavigationController({
collections: [productsCollection],
authController,
dataSourceDelegate: mongoDataSourceDelegate
});
```
#### Rendering the Application
Finally, wire everything up inside the return statement of your component.
```jsx
const MongoDBApp = () => {
const name = "My FireCMS App";
const { firebaseApp, firebaseConfigLoading, configError } = useInitialiseFirebase({ firebaseConfig });
const { app } = useInitRealmMongodb(atlasConfig);
const modeController = useBuildModeController();
const userConfigPersistence = useBuildLocalConfigurationPersistence();
const authController: MongoAuthController = useMongoDBAuthController({ app });
const mongoDataSourceDelegate = useMongoDBDelegate({
app,
cluster: "mongodb-atlas",
database: "todo"
});
const storageSource = useFirebaseStorageSource({ firebaseApp });
const { authLoading, canAccessMainView, notAllowedError } = useValidateAuthenticator({
authController,
authenticator: () => true, // Replace with your logic
dataSourceDelegate: mongoDataSourceDelegate,
storageSource
});
const navigationController = useBuildNavigationController({
collections: [productsCollection],
authController,
dataSourceDelegate: mongoDataSourceDelegate
});
if (firebaseConfigLoading || !firebaseApp) {
return ;
}
if (configError) {
return {configError} ;
}
return (
{({ context, loading }) => {
if (loading || authLoading) {
return ;
}
if (!canAccessMainView) {
return (
);
}
return (
);
}}
);
};
export default MongoDBApp;
```
## Licensing
:::tip
Do you have any questions, would like to request a trial, or need a custom license?
Please [contact us via email](mailto:hello@firecms.co),
or [schedule a call](https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ0INW8ihjQ90S4gkdo8_rbL_Zx7gagZShLIpHyW43zDXkQDPole6a1coo1sT2O6Gl05X8lxFDlp?gv=true).
:::
You can use the FireCMS PRO features locally during development without a license, but you will need to
purchase one to deploy it. There is a grace period after the first deployment
to allow you to test it in production.
You can purchase a license in the [FireCMS subscriptions site](https://app.firecms.co/subscriptions).
When creating your license, you need to specify the project IDs that will be
using the license. You can find your project ID in the Firebase console.
You will receive an API key that you need to pass to your `FireCMS` component.
If you are using a starter template, you can set it in the `.env` file.
## User Management

Control who can access your **Firebase admin panel** and what they can do. The User Management Plugin provides a complete **role-based access control (RBAC)** system for your FireCMS project.
:::tip[Why use the User Management Plugin?]
Building authentication and permissions from scratch is time-consuming and error-prone. This plugin gives you:
- **User CRUD**: Create, edit, and delete CMS users directly in the UI
- **Role definitions**: Define granular permissions (Admin, Editor, Viewer, or custom)
- **Per-collection permissions**: Control who can read, create, edit, or delete in each collection
- **Firestore integration**: All user/role data stored securely in your **Firestore** database
:::
### Installation
First, ensure you have installed the necessary dependencies.
To use the plugin, you need to have FireCMS and Firebase set up in your project.
```sh
yarn add @firecms/user_management
```
### Configuration
The plugin requires several configurations, including paths for storing user and role data.
The default config of the plugin will be saved to your database under the path `__FIRECMS/config`,
but you can customize this path as needed.
If you are using Firestore, make sure to set up the Firestore rules to allow the plugin to read and write to the specified paths.
We suggest adding these rules to your Firestore security rules, which will allow users with the correct roles to access the user management data.
```
match /{document=**} {
allow read: if isFireCMSUser();
allow write: if isFireCMSUser();
}
function isFireCMSUser(){
return exists(/databases/$(database)/documents/__FIRECMS/config/users/$(request.auth.token.email));
}
```
#### User Management Parameters
Below are the parameters you can configure:
- **firebaseApp**: The Firebase app to use for user management.
- **usersPath**: Path in Firestore where user configurations are stored. Default is `__FIRECMS/config/users`.
- **rolesPath**: Path in Firestore where role configurations are stored. Default is `__FIRECMS/config/roles`.
- **allowDefaultRolesCreation**: If there are no roles in the database, show a button to create default roles. Default is `true`.
#### Hook Usage
The main hook to initialize the plugin's functionality is `useBuildUserManagement`. Here's an example of how to use it:
```jsx
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate,
usersPath: "__FIRECMS/config/users",
rolesPath: "__FIRECMS/config/roles",
allowDefaultRolesCreation: true,
});
```
### Setting up the Plugin
To integrate the user management into FireCMS, use the `useUserManagementPlugin` function and pass the
resulting plugin into the FireCMS configuration. You will usually want to do this in your main App component.
#### Example Configuration
```jsx
function App() {
// .. rest of your configuration, including the `authController` and `firestoreDelegate`
const userManagementPlugin = useUserManagementPlugin({ userManagement });
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate,
usersPath: "__FIRECMS/config/users",
rolesPath: "__FIRECMS/config/roles",
allowDefaultRolesCreation: true,
includeCollectionConfigPermissions: true,
});
const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
disabled: userManagement.loading,
authController,
authenticator: userManagement.authenticator,
dataSourceDelegate: firestoreDelegate,
storageSource
});
const plugins = [userManagementPlugin];
const navigationController = useBuildNavigationController({
collections,
views,
adminViews: userManagementAdminViews,
collectionPermissions: userManagement.collectionPermissions, // Optional, link collection permissions to user management
authController,
dataSourceDelegate: firestoreDelegate,
plugins
});
return (
// ..components and providers
);
}
export default App;
```
### Adding the user management views
Besides the plugin, you will need to add the user management views to your FireCMS project.
They are exported as an array from the `@firecms/user_management` package.
You can add them to your `useBuildNavigationController` hook configuration, in the `adminViews` array.
```jsx
const navigationController = useBuildNavigationController({
collections,
views,
adminViews: userManagementAdminViews,
collectionPermissions: userManagement.collectionPermissions,
authController,
dataSourceDelegate: firestoreDelegate
});
````
### Authenticating Users
The plugin provides an `authenticator` function that you can use to authenticate users based on their roles.
You can pass this function to the `useValidateAuthenticator` hook to authenticate users and determine if they can access the main view.
```jsx
const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
disabled: userManagement.loading,
authController,
authenticator: userManagement.authenticator,
dataSourceDelegate: firestoreDelegate,
storageSource
});
```
The result of the `useValidateAuthenticator` hook can be used to return the main view or an error message
if the user is not allowed. Please note that you can override any part of the user management configuration
to customize the authentication process.
### Integrating Collection Permissions
The `UserManagement` object includes a `collectionPermissions` function that checks what operations a
user can perform based on their roles and the collection configuration.
The permissions will be based on your users and roles configuration in Firestore.
You can pass this function to the `useBuildNavigationController` hook to integrate collection
permissions into your FireCMS project.
Note that if you apply permissions to a collection,they will override the permissions set in the collection configuration.
```jsx
const navigationController = useBuildNavigationController({
collections,
collectionPermissions: userManagement.collectionPermissions,
authController,
dataSourceDelegate: firestoreDelegate
});
```
### Error Handling
The plugin provides error handling through `rolesError` and `usersError` properties in the `UserManagement` object. These can be used to detect and display error messages when loading roles or users.
```jsx
if (userManagement.rolesError) {
console.error("Roles Error: ", userManagement.rolesError);
// Handle roles error
}
if (userManagement.usersError) {
console.error("Users Error: ", userManagement.usersError);
// Handle users error
}
```
### Using the plugin within your application
Once you have set up the plugin, you will have created a provider that you can use to manage users and roles within
your application. You can access the user management functions and data through the `useUserManagement` hook.
The `userManagement` object returned by the `useUserManagement` hook includes the following properties:
- **`loading`**: Indicates if the data is being loaded. Boolean value.
- **`users`**: Array of user objects. Contains the users being managed.
- **`saveUser`**: Function to persist a user. Takes a `user` object and returns a promise resolving with the saved user.
- **`deleteUser`**: Function to delete a user. Takes a `user` object and returns a promise resolving when the user is deleted.
- **`roles`**: Array of role objects. Contains the roles available in the system.
- **`saveRole`**: Function to persist a role. Takes a `role` object and returns a promise resolving when the role is saved.
- **`deleteRole`**: Function to delete a role. Takes a `role` object and returns a promise resolving when the role is deleted.
- **`isAdmin`**: Optional. Boolean to check if the logged-in user is an Admin.
- **`allowDefaultRolesCreation`**: Optional. Include a button to create default roles, in case there are no roles in the system. Boolean value.
- **`includeCollectionConfigPermissions`**: Optional. Include the collection config permissions in the user management system. Boolean value.
- **`collectionPermissions`**: A permissions builder that defines which operations can be performed by a user in a collection. The permission builder generated should be based on the user roles and the collection config.
- **`defineRolesFor`**: Function to define the roles for a given user. You will typically want to plug this into your auth controller. Takes a `user` object and returns a promise resolving with an array of roles or undefined.
- **`authenticator`**: Optional. Authenticator callback built from the current configuration of the user management. It will only allow access to users with the required roles.
- **`rolesError`**: Optional. Holds any error occurred while loading roles.
- **`usersError`**: Optional. Holds any error occurred while loading users.
## Collection Editor UI

This document describes how to use the **Collection Editor UI Plugin** with **FireCMS** to manage and configure your
Firestore collections. The Collection Editor UI Plugin provides an interface for creating, editing, and organizing
collections, with support for customizable permissions and configuration options.
Typically, collections in FireCMS are defined in code, and passed as a prop to the `NavigationController` on
initialization. The Collection Editor UI Plugin allows you to manage collections directly in the application, providing
a more user-friendly and flexible way to organize and configure your Firestore collections.
In this document, we will cover how to set up and use this plugin in your FireCMS application.
### Installation
First, ensure you have installed the necessary dependencies. To use the Collection Editor UI Plugin, you need to have
FireCMS and Firebase set up in your project.
```sh
yarn add @firecms/collection_editor
```
or
```sh
npm install @firecms/collection_editor
```
### Configuration
The plugin requires several configurations, including controllers for managing collection configurations, permissions,
and custom views.
#### Default Configuration
The Collection Editor UI Plugin integrates with your Firestore backend to store and manage collection configurations. By
default, configurations are managed internally, but you can customize paths and behaviors as needed.
#### Firestore Security Rules
Ensure that your Firestore security rules allow the plugin to read and write to the configuration paths. Below is an
example of security rules that permit authenticated users to access the collection configurations:
```txt
match /{document=**} {
allow read: if isFireCMSUser();
allow write: if isFireCMSUser();
}
function isFireCMSUser(){
return exists(/databases/$(database)/documents/__FIRECMS/config/collections/$(request.auth.uid));
}
```
### Collection Configuration Plugin
The Collection Editor UI Plugin allows you to include a UI for editing collection configurations. You can choose where
the configuration is stored and pass the configuration to the plugin. The plugin includes a controller that saves the
configuration in your Firestore database. The default path is `__FIRECMS/config/collections`.
The controller includes methods you can use in your components to manage the collection configuration.
```jsx
const collectionConfigController = useFirestoreCollectionsConfigController({
firebaseApp
});
```
You can define your collections in code or use the UI to define them. It is also possible to allow modification in the
UI of collections defined in code. You can then merge the collections defined in code with those defined in the UI.
```jsx
// The collection builder is passed to the navigation controller
const collectionsBuilder = useCallback(() => {
// Define a sample collection in code.
const collections = [
productsCollection
// Your collections here
];
// Merge collections defined in the collection editor (UI) with your own collections
return mergeCollections(collections, collectionConfigController.collections ?? []);
}, [collectionConfigController.collections]);
```
To add the Collection Editor UI Plugin, include it in the list of plugins passed to the `FireCMS` component.
```jsx
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController
});
```
This will add an icon in each collection card that allows you to edit the collection configuration.
### Hook Usage
The main hook to utilize the plugin's functionality is `useCollectionEditorPlugin`. Here's an example of how to use it:
```jsx
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController,
configPermissions: customPermissionsBuilder,
reservedGroups: ["admin"],
getData: async (path, parentPaths) => {
// Fetch and return data for the given path
return fetchDataForPath(path, parentPaths);
},
getUser: (uid) => {
// Retrieve and return user data based on UID
return getUserById(uid);
},
onAnalyticsEvent: (event, params) => {
// Handle analytics events
logAnalyticsEvent(event, params);
}
});
```
### Setting up the Plugin
To integrate the Collection Editor UI Plugin into FireCMS, use the `useCollectionEditorPlugin` hook and pass the
resulting plugin into the FireCMS configuration. This is typically done in your main App component.
#### Example Configuration
```jsx
function App() {
const {
firebaseApp,
firebaseConfigLoading,
configError
} = useInitialiseFirebase({
firebaseConfig
});
const firestoreDelegate = useFirestoreDelegate({
firebaseApp
});
const authController = useFirebaseAuthController({
firebaseApp,
signInOptions: ["google.com", "password"]
});
const collectionConfigController = useFirestoreCollectionsConfigController({
firebaseApp
});
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController,
configPermissions: customPermissionsBuilder,
reservedGroups: ["admin"],
extraView: {
View: CustomCollectionView,
icon:
}
});
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate,
authController: authController
});
const userManagementPlugin = useUserManagementPlugin({ userManagement });
const collectionsBuilder = useCallback(() => {
// Define your own collections
const collections = [
productsCollection,
// Add other collections here
];
// Merge with collections defined via the Collection Editor UI
return mergeCollections(collections, collectionConfigController.collections ?? []);
}, [collectionConfigController.collections]);
const plugins = [
collectionEditorPlugin,
userManagementPlugin
];
const navigationController = useBuildNavigationController({
collections: collectionsBuilder(),
views: customViews,
adminViews: userManagementAdminViews,
collectionPermissions: collectionEditorPlugin.collectionPermissions,
authController,
dataSourceDelegate: firestoreDelegate,
plugins
});
const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
disabled: collectionEditorPlugin.loading,
authController: authController,
authenticator: customAuthenticator,
dataSourceDelegate: firestoreDelegate,
storageSource
});
if (firebaseConfigLoading) {
return ;
}
if (configError) {
return ;
}
return (
{({
context,
loading
}) => {
if (loading || authLoading) {
return ;
}
if (!canAccessMainView) {
return ;
}
return ;
}}
);
}
export default App;
```
### Adding the Collection Editor Views
The Collection Editor UI Plugin provides custom views that need to be added to your FireCMS project. These views are
integrated into the FireCMS navigation and allow users to manage collections.
#### Example Integration
```jsx
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController,
configPermissions: customPermissionsBuilder,
reservedGroups: ["admin"],
extraView: {
View: CustomCollectionView,
icon:
}
});
// Include the plugin in your FireCMS configuration
{/* Your application components */}
```
### Authenticating Users
The Collection Editor UI Plugin integrates with your authentication system to ensure that only authorized users can
manage collections. You can use the `useValidateAuthenticator` hook to authenticate users and determine their access
levels.
#### Example Usage
```jsx
const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
disabled: collectionEditorPlugin.loading,
authController: authController,
authenticator: customAuthenticator,
dataSourceDelegate: firestoreDelegate,
storageSource: storageSource
});
if (authLoading) {
return ;
}
if (!canAccessMainView) {
return ;
}
// Render your main application view
```
### Integrating Collection Permissions
The Collection Editor UI Plugin includes a `collectionPermissions` function that determines what operations a user can
perform based on their roles and the collection configuration. This function ensures that users have appropriate access
rights throughout your FireCMS project.
#### Example Integration
```jsx
const navigationController = useBuildNavigationController({
collections: customCollections,
views: customViews,
adminViews: userManagementAdminViews,
collectionPermissions: collectionEditorPlugin.collectionPermissions,
authController,
dataSourceDelegate: firestoreDelegate
});
```
**Note:** Applying permissions to a collection overrides the permissions set in the collection configuration.
### Error Handling
The plugin provides error handling through properties such as `configError` and `collectionErrors` in the
`CollectionEditor` object. These can be used to detect and display error messages when loading or managing collections.
#### Example Error Handling
```jsx
if (collectionEditorPlugin.configError) {
return ;
}
if (collectionEditorPlugin.collectionErrors) {
return ;
}
```
### Using the Plugin within Your Application
Once you have set up the Collection Editor UI Plugin, you will have access to tools and functions for managing your
Firestore collections. You can access the collection management functions and data through the
`useCollectionEditorPlugin` hook.
#### Collection Editor Object
The `collectionEditor` object returned by the `useCollectionEditorPlugin` hook includes the following properties:
- **`loading`**: Indicates if the collection data is being loaded. Boolean value.
- **`collections`**: Array of collection objects. Contains the collections being managed.
- **`saveCollection`**: Function to persist a collection. Takes a `collection` object and returns a promise resolving
with the saved collection.
- **`deleteCollection`**: Function to delete a collection. Takes a `collection` object and returns a promise resolving
when the collection is deleted.
- **`configError`**: Holds any error that occurred while loading collection configurations.
- **`collectionPermissions`**: Function that defines the permissions for collections based on user roles and collection
configurations.
- **`createCollection`**: Function to initiate the creation of a new collection.
- **`reservedGroups`**: Array of group names that are reserved and cannot be used in collection names.
- **`extraView`**: Custom view added to the FireCMS navigation for collection management.
- **`defineRolesFor`**: Function to define roles for a given user, typically integrated into your auth controller.
- **`authenticator`**: Optional. Authenticator callback built from the current configuration of the collection editor.
It will only allow access to users with the required roles.
#### Example Access
```jsx
const collectionEditor = useCollectionEditorPlugin({
collectionConfigController,
configPermissions: customPermissionsBuilder,
reservedGroups: ["admin"]
});
// Use collectionEditor properties and functions
if (collectionEditor.loading) {
return ;
}
return (
{collectionEditor.collections.map(collection => (
))}
);
```
### Advanced Configuration
#### Custom Components
You can modify the UI and functionality of the Collection Editor UI Plugin by providing custom UI components. For
example, customizing the database field renderer:
```jsx
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController,
components: {
DatabaseField: CustomDatabaseFieldComponent
}
});
```
#### Custom Permissions Builder
Define custom permissions logic to control what users can do within the collection editor:
```jsx
const customPermissionsBuilder = ({ user }) => ({
createCollections: user?.isAdmin === true,
editCollections: user?.roles.includes("editor"),
deleteCollections: user?.isAdmin === true
});
```
### Example Usage
Below is an example of how to integrate the Collection Editor UI Plugin into a FireCMS application.
#### Plugin Setup
```jsx
export function App() {
const title = "My CMS app";
const {
firebaseApp,
firebaseConfigLoading,
configError
} = useInitialiseFirebase({
firebaseConfig
});
/**
* Controller used to save the collection configuration in Firestore.
* Note that this is optional and you can define your collections in code.
*/
const collectionConfigController = useFirestoreCollectionsConfigController({
firebaseApp
});
const collectionsBuilder = useCallback(() => {
// Here we define a sample collection in code.
const collections = [
productsCollection
// Your collections here
];
// You can merge collections defined in the collection editor (UI) with your own collections
return mergeCollections(collections, collectionConfigController.collections ?? []);
}, [collectionConfigController.collections]);
const signInOptions: FirebaseSignInProvider[] = ["google.com", "password"];
/**
* Controller used to manage the dark or light color mode
*/
const modeController = useBuildModeController();
/**
* Delegate used for fetching and saving data in Firestore
*/
const firestoreDelegate = useFirestoreDelegate({
firebaseApp
})
/**
* Controller used for saving and fetching files in storage
*/
const storageSource = useFirebaseStorageSource({
firebaseApp
});
/**
* Controller for managing authentication
*/
const authController: FirebaseAuthController = useFirebaseAuthController({
firebaseApp,
signInOptions,
});
/**
* Controller for saving some user preferences locally.
*/
const userConfigPersistence = useBuildLocalConfigurationPersistence();
/**
* Use the authenticator to control access to the main view
*/
const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
authController,
dataSourceDelegate: firestoreDelegate,
storageSource
});
const navigationController = useBuildNavigationController({
collections: collectionsBuilder,
authController,
dataSourceDelegate: firestoreDelegate
});
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController
});
if (firebaseConfigLoading || !firebaseApp) {
return ;
}
if (configError) {
return <>{configError}>;
}
return (
{({
context,
loading
}) => {
let component;
if (loading || authLoading) {
component = ;
} else {
if (!canAccessMainView) {
component = (
);
} else {
component = (
);
}
}
return component;
}}
);
}
```
## Data Export

Export your **Firestore** data directly from your **admin panel**. The Data Export Plugin adds one-click JSON and CSV export to any FireCMS collection.
:::tip[Common use cases]
- **Backups**: Create regular snapshots of your data
- **Migrations**: Move data between environments or databases
- **Reporting**: Feed data into spreadsheets or BI tools
- **Compliance**: Export data for audits or GDPR requests
:::
### Installation
First, ensure you have installed the necessary dependencies. To use the plugin, you need to have FireCMS set up in your
project.
```sh
yarn add @firecms/data_export
```
or
```sh
npm install @firecms/data_export
```
### Configuration
The plugin requires minimal configuration and can be easily integrated into your FireCMS setup. You can customize the
export behavior using the `ExportPluginProps`.
#### ExportPluginProps Parameters
Below are the parameters you can configure:
- **`exportAllowed`**: A function that determines whether exporting is allowed based on the provided parameters.
- **Type**: `(props: ExportAllowedParams) => boolean`
- **Default**: `undefined` (exporting is allowed by default)
- **`notAllowedView`**: A React node to display when exporting is not permitted.
- **Type**: `React.ReactNode`
- **Default**: `undefined`
- **`onAnalyticsEvent`**: A callback function triggered on analytics events related to exporting.
- **Type**: `(event: string, params?: any) => void`
- **Default**: `undefined`
#### ExportAllowedParams
The `ExportAllowedParams` type provides context for the `exportAllowed` function:
- **`collectionEntitiesCount`**: The number of entities in the collection.
- **Type**: `number`
- **`path`**: The path of the collection in FireCMS.
- **Type**: `string`
- **`collection`**: The collection entity.
- **Type**: `EntityCollection`
### Hook Usage
The main hook to utilize the plugin's functionality is `useExportPlugin`. Here's an example of how to use it:
```jsx
function App() {
const exportPlugin = useExportPlugin({
exportAllowed: ({
collectionEntitiesCount,
path,
collection
}) => {
// Example: Allow export only if there are more than 10 entities
return collectionEntitiesCount > 10;
},
notAllowedView: Exporting is not permitted.,
onAnalyticsEvent: (event, params) => {
console.log(`Export Event: ${event}`, params);
},
});
const plugins = [exportPlugin];
const navigationController = useBuildNavigationController({
// ... rest of your config
plugins
});
return (
{({ context, loading }) => {
// ... your components
}}
);
}
export default App;
```
### Setting up the Plugin
To integrate the Data Export Plugin into FireCMS, use the `useExportPlugin` hook and pass the resulting plugin into the
FireCMS configuration. You will typically want to do this in your main App component.
### Using the Export Functionality
Once the plugin is integrated, you can use the export functionality directly within your collection views. The plugin
adds export actions to your collection views, allowing users to export data as JSON or CSV.
#### Example: Exporting a Collection
1. Navigate to the desired collection in your FireCMS application.
2. Click on the **Export** action in the collection actions toolbar.
3. Choose the desired export format (JSON or CSV).
4. The exported file will be downloaded to your device.
### Customizing the Export Behavior
You can customize how the export functionality behaves by providing custom implementations for the `exportAllowed`,
`notAllowedView`, and `onAnalyticsEvent` props.
#### Example: Restricting Export Based on User Role
```jsx
const exportPlugin = useExportPlugin({
exportAllowed: ({
collection,
path,
collectionEntitiesCount
}) => {
// Allow export only for admins
return userRoles.includes('admin');
},
notAllowedView: Only administrators can export data.,
onAnalyticsEvent: (event, params) => {
// Log export events for auditing
logAnalytics(event, params);
},
});
```
### Types
#### `ExportPluginProps`
Defines the properties that can be passed to the `useExportPlugin` hook.
```typescript
export type ExportPluginProps = {
exportAllowed?: (props: ExportAllowedParams) => boolean;
notAllowedView?: React.ReactNode;
onAnalyticsEvent?: (event: string, params?: any) => void;
}
```
#### `ExportAllowedParams`
Provides context to determine if exporting is allowed.
```typescript
export type ExportAllowedParams = {
collectionEntitiesCount: number;
path: string;
collection: EntityCollection;
};
```
## Data Import

The **Data Import Plugin** for FireCMS enables you to import collection data from JSON, CSV, XLSL (Excel) files directly
into your
FireCMS application. This plugins provide an interface where users can upload files and map the existing data to the
collection properties. This makes it very convenient to move data from one service to another and convert data into
the right data types in the database.
The plugin is able to do automatic conversion of some data types such as dates.
The import feature can be also used within the collection editor plugin. In the collection editor, you can create
new collections from a data file. It will be able to understand your data structure correctly, and even infer
types such as as dates or enums (even if they are stored as strings).
### Installation
First, install the Data Import Plugin package:
```sh
yarn add @firecms/data_import
```
### Configuration
Integrate the Data Import Plugin using the `useImportPlugin` hook. You can optionally provide `ImportPluginProps` to
customize its behavior.
#### ImportPluginProps
- **`onAnalyticsEvent`**: A callback triggered on import-related analytics events.
- **Type**: `(event: string, params?: any) => void`
- **Default**: `undefined`
### Hook Usage
Use the `useImportPlugin` hook to create the import plugin and include it in the FireCMS configuration.
#### Example: Integrating the Data Import Plugin
```jsx
export function App() {
const importPlugin = useImportPlugin({
onAnalyticsEvent: (event, params) => {
console.log(`Import Event: ${event}`, params);
// Integrate with your analytics service if needed
},
});
return (
{({ context, loading }) => {
// ... your components
}}
);
}
export default App;
```
### Using the Import Functionality
After integration, the import feature is available within your collection views. Users can upload JSON or CSV files to
populate the collections.
#### Steps to Import Data
1. **Navigate to a Collection**: Open the desired collection in your FireCMS application.
2. **Initiate Import**: Click on the **Import** action in the collection actions toolbar.
3. **Upload File**: Select and upload the JSON or CSV file containing the data.
4. **Data Type Mapping**: Select the data types and how your data should be mapped to the current structure.
4. **Data Processing**: The plugin processes the file and adds the data to your collection.
### Types
#### `ImportPluginProps`
Defines the properties for the `useImportPlugin` hook.
```typescript
export type ImportPluginProps = {
onAnalyticsEvent?: (event: string, params?: any) => void;
}
```
#### `ImportAllowedParams`
Provides context for determining import permissions.
```typescript
export type ImportAllowedParams = {
collectionEntitiesCount: number;
path: string;
collection: EntityCollection;
};
```
### Example: Tracking Imports with Google Analytics
```jsx
const importPlugin = useImportPlugin({
onAnalyticsEvent: (event, params) => {
if (window && window.gtag) {
window.gtag('event', event, params);
}
},
});
```
## Entity History
This plugin adds a history view to your entities in FireCMS. It allows you to track changes made to entities over time, showing who made the change and when.
### Installation
```bash
npm install @firecms/entity_history
## or
yarn add @firecms/entity_history
```
### Features
- Adds a "History" tab to entity detail views.
- Displays a log of changes for each entity.
- Can be enabled globally or on a per-collection basis.
- Allows customization of user display in the history log.
### Basic Usage
To use the plugin, import `useEntityHistoryPlugin` and add it to your `FirebaseCMSApp` plugins array.
If you are using the user management plugin, you can provide a function to resolve user details from a UID.
You can also pass your own custom `getUser` function to fetch user details.
```tsx
export default function App() {
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate,
authController: authController
});
const entityHistoryPlugin = useEntityHistoryPlugin({
// Enable history for all collections by default
// This can be overridden by setting `history: false` in a specific collection
defaultEnabled: true,
// Provide a function to resolve user details from a UID
getUser: userManagement.getUser
});
const plugins = [entityHistoryPlugin];
const navigationController = useBuildNavigationController({
// ... rest of your config
plugins
});
return (
{({ context, loading }) => {
// ... your components
}}
);
}
```
By default, the history feature is not enabled for any collection. You need to enable it explicitly in your collection configuration:
```tsx
const productsCollection = buildCollection({
name: "Products",
path: "products",
properties: {
name: {
name: "Name",
dataType: "string"
}
// ...other properties
},
history: true // Enable history for this collection
});
```
### Configuration Options
| Option | Type | Description |
| ---------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `defaultEnabled` | `boolean` | If `true`, the history view will be enabled for all collections by default. Each collection can override this by setting its `history` property. |
| `getUser` | `(userId: string) => User \| null` | Optional function to get the user object (display name, photo, etc.) from a user ID to display in the history log. |
### Collection Configuration
To enable or disable history for a specific collection, set the `history` property in the collection configuration:
```tsx
const sampleCollection = buildCollection({
// ...
history: true // or false
});
```
### Additional Notes
- The plugin relies on callbacks to record entity changes. Ensure that your data persistence logic correctly triggers these callbacks.
- The `getUser` function is crucial for displaying meaningful user information in the history log. If not provided, only user IDs might be shown.
### Programmatic History Creation
For advanced use cases where you need to create history entries programmatically (outside of the normal save callbacks), you can use the `createHistoryEntry` function:
```tsx
// Example: Creating a history entry when importing data
const handleDataImport = async (context: FireCMSContext, importedData: any[]) => {
for (const item of importedData) {
// Save the entity first
await context.dataSource.saveEntity({
path: "products",
entityId: item.id,
values: item,
status: "new"
});
// Create a history entry for the import action
createHistoryEntry({
context: context,
previousValues: undefined, // No previous values for new entities
values: item,
path: "products",
entityId: item.id
});
}
};
// Example: Creating a history entry for a custom update operation
const handleCustomUpdate = async (context: FireCMSContext, entityId: string, oldValues: any, newValues: any) => {
// Perform your custom update logic here
await context.dataSource.saveEntity({
path: "products",
entityId: entityId,
values: newValues,
status: "existing"
});
// Create a history entry to track the change
createHistoryEntry({
context: context,
previousValues: oldValues,
values: newValues,
path: "products",
entityId: entityId
});
};
```
#### Parameters
| Parameter | Type | Description |
| ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context` | `FireCMSContext` | The FireCMS context object containing the data source and auth controller |
| `previousValues` | `Partial` (optional) | The previous values of the entity. If not provided, the history entry will be marked as a creation rather than an update |
| `values` | `Partial` | The current/new values of the entity |
| `path` | `string` | The collection path where the entity is stored |
| `entityId` | `string` | The unique identifier of the entity |
#### History Entry Structure
Each history entry is automatically stored in a subcollection `__history` under the entity and includes:
- All entity values at the time of the change
- `__metadata` object containing:
- `previous_values`: The previous state of the entity (if provided)
- `changed_fields`: Array of field paths that were modified
- `updated_on`: Timestamp of when the change occurred
- `updated_by`: User ID of who made the change (if authenticated)
## Defining Custom Storage in FireCMS
FireCMS provides flexibility when it comes to integrating custom storage solutions.
While it comes with built-in support for Firebase Storage, you might want to define your
own storage solutions to handle file uploads, downloads, and metadata management.
This guide will help you set up a custom storage solution.
### Creating a Custom Storage Source
A custom storage source in FireCMS is defined by implementing the `StorageSource` interface.
Here is a template for creating a custom storage source.
Beware: This is an example on how you can implement and use S3, directly from a custom storage source.
This is not the preferred way of using S3 since it'd discourage to consume it directly from the frontend.
As you will have to insert the API key and secret in the frontend, which is not secure.
Instead, you could use [Amplify/Storage](https://docs.amplify.aws/react/build-a-backend/storage/) or a custom IAM auth.
Below is a template for creating a custom storage source:
```tsx
export interface S3StorageSourceProps {
apiKey: string;
apiSecret: string;
region: string;
defaultBucket?: string;
}
function initializeCustomClient({apiKey, apiSecret, region, defaultBucket}: S3StorageSourceProps) {
const s3Client = new S3Client({region, credentials: {accessKeyId: apiKey, secretAccessKey: apiSecret}});
return {
uploadFile: async (destinationPath: string, file: File, bucket?: string, metadata?: any) => {
await s3Client.send(new PutObjectCommand({
Bucket: bucket || defaultBucket,
Key: destinationPath,
Body: file,
ContentType: metadata?.contentType,
Metadata: metadata
}));
return {
path: destinationPath,
bucket: bucket || defaultBucket || ""
}
},
getFile: async (path: string, bucket?: string) => {
return await s3Client.send(new GetObjectCommand({
Bucket: bucket || defaultBucket || "",
Key: path
}));
},
getDownloadURL: async (path: string, bucket?: string) => {
const command = new GetObjectCommand({Bucket: bucket || defaultBucket, Key: path});
return await getSignedUrl(s3Client, command, {expiresIn: 3600});
},
getMetadata: async (path: string, bucket?: string) => {
const s3Object = await s3Client.send(new GetObjectCommand({
Bucket: bucket || defaultBucket,
Key: path
}));
return {
bucket: (bucket || defaultBucket) ?? "",
fullPath: path,
name: path,
size: s3Object.ContentLength || 0,
contentType: s3Object.ContentType || "",
customMetadata: s3Object.Metadata || {}
}
}
};
}
export function useCustomStorageSource(props: S3StorageSourceProps): StorageSource {
// Initialize your storage client based on the props provided
// For example, it could be a client for Amazon S3, Google Cloud Storage, etc.
const customClient = initializeCustomClient(props);
return {
async uploadFile({file, fileName, path, metadata, bucket}: UploadFileProps): Promise {
const usedFilename = fileName ?? file.name;
const destinationPath = `${path}/${usedFilename}`;
// Logic to upload file using your storage client
return await customClient.uploadFile(destinationPath, file, bucket, metadata);
},
async getFile(path: string, bucket?: string): Promise {
const targetBucket = bucket ?? props.defaultBucket;
try {
// Logic to retrieve the file using your storage client
const fileData = await customClient.getFile(path, targetBucket);
if (fileData && fileData.Body) {
const byteArray = await fileData.Body.transformToByteArray();
const blob = new Blob([byteArray], {type: fileData.ContentType});
return new File([blob], path);
} else {
return null;
}
} catch (e) {
return null; // File not found
}
},
async getDownloadURL(path: string, bucket?: string): Promise {
const targetBucket = bucket || props.defaultBucket;
try {
// Logic to get the download URL using your storage client
const url = await customClient.getDownloadURL(path, targetBucket);
const metadata = await customClient.getMetadata(path, targetBucket);
return {url, metadata};
} catch (e) {
return {url: null, fileNotFound: true};
}
}
};
}
```
### Using the Custom Storage Source
After creating the custom storage source, you can use it in your FireCMS application by initializing it in your component and passing it to the `FireCMS` component.
#### Example Usage
Here is an example of how to use the custom storage source in your FireCMS application
```tsx
const customStorageConfig: CustomStorageSourceProps = {
apiKey: "your-api-key",
apiSecret: "your-api-secret",
region: "your-region",
defaultBucket: "your-bucket-name"
// ... other necessary properties
};
const CustomStorageApp: React.FC = () => {
const name = "My Custom Storage FireCMS App";
const modeController = useBuildModeController();
const userConfigPersistence = useBuildLocalConfigurationPersistence();
const storageSource = useCustomStorageSource(customStorageConfig);
// const authController = useFirebaseAuthController(); // your auth controller
// const dataSourceDelegate = {}; // Your data source delegate implementation
const navigationController = useBuildNavigationController({
collections: [productsCollection],
// authController,
// dataSourceDelegate
});
// if (authLoading) {
// return ;
// }
return (
{({ context, loading }) => {
if (loading || authLoading) {
return ;
}
if (!canAccessMainView) {
return {notAllowedError} ;
}
return (
);
}}
);
};
export default CustomStorageApp;
```
As this example uses AWS S3, you will also need to enable cors in the bucket,
as described in the [AWS documentation - Configuring cross-origin resource sharing (CORS)](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html?icmpid=docs_amazons3_console).
```json
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"DELETE"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [
"x-amz-server-side-encryption",
"x-amz-request-id",
"x-amz-id-2"
],
"MaxAgeSeconds": 3000
}
]
```
This documentation provides a clear guide for defining custom storage solutions in FireCMS. Follow the template to integrate other storage services as per your requirements.
## Building a custom backend
FireCMS internally uses 3 main controllers to manage the data, file storage and authentication.
These controllers are designed to be easily extended and replaced with your own implementations.
FireCMS provides default implementations for Firebase, Firestore and Firebase Authentication,
but you can replace them with your own implementations. We also provide an integration with MongoDB Atlas.
### DataSourceDelegate
The `DataSourceDelegate` is the delegate responsible for managing the data source. The delegate will
be passed to FireCMS and will be used internally by the `DataSource`.
You can retrieve the data source in any component using the `useDataSource` hook. You can also access the data source
from callbacks where there is a `context` object defined, under `context.dataSource`.
FireCMS provides default implementations for:
- Firebase `useFirestoreDelegate` (package `@firecms/firebase`)
- MongoDB `useMongoDBDelegate` (package `@firecms/mongodb`)
#### Creating your own DataSourceDelegate
If you want to create your own `DataSourceDelegate`, you will need to implement the following methods:
**fetchCollection**: Used to fetch a collection of entities from your data source. Accepts various parameters
like `path`, `filter`, `limit`, etc.
**listenCollection**: (Optional) Listen for real-time updates on a collection. Returns a function to cancel the
subscription. If not implemented, the `fetchCollection` method will be used instead.
**fetchEntity**: Fetch a single entity based on `path` and `entityId`.
**listenEntity**: (Optional) Listen for real-time updates on a single entity. Returns a function to cancel the
subscription. If not implemented, the `fetchEntity` method will be used instead.
**saveEntity**: Save or update an entity at a specific path.
**deleteEntity**: Delete an entity by providing the entity to delete.
**checkUniqueField**: Check the uniqueness of a particular field in a collection.
**generateEntityId**: Generate a unique ID for a new entity.
**countEntities**: (Optional) Count the number of entities in a collection.
**isFilterCombinationValid**: (Optional) Check if a given filter combination is valid.
**currentTime**: (Optional) Get the current timestamp object.
**delegateToCMSModel**: Convert data from the source model to CMS model.
**cmsToDelegateModel**: Convert data from the CMS model to the source model.
**initTextSearch**: (Optional) Initialize text search capabilities.
### StorageSource
The `StorageSource` is the controller responsible for managing the file storage. The delegate will
be passed to FireCMS and will be used internally by CMS.
You can access the storage source in any component using the `useStorageSource` hook. You can also access the storage
source from callbacks where there is a `context` object defined, under `context.storageSource`.
FireCMS provides default implementations for:
- Firebase `useFirebaseStorageSource` (package `@firecms/firebase`)
#### Description of Methods
**uploadFile**: Upload a file to storage, specifying a name and a path. Accepts parameters
like `file`, `fileName`, `path`, `metadata`, and `bucket`.
**getDownloadURL**: Convert a storage path or URL into a download configuration. Accepts `pathOrUrl` and
optionally `bucket`.
**getFile**: Retrieve a file from a storage path. Returns `null` if the file does not exist. Accepts `path` and
optionally `bucket`.
### AuthController
The `AuthController` is the controller responsible for managing the authentication. The delegate will
be passed to FireCMS and will be used internally by CMS.
You can access the auth controller in any component using the `useAuthController` hook.
You can also access the auth controller from callbacks where there is a `context` object defined,
under `context.authController`.
FireCMS provides default implementations for:
- Firebase `useFirebaseAuthController` (package `@firecms/firebase`)
- MongoDB `useMongoDBAuthController` (package `@firecms/mongodb`)
#### Description of Properties and Methods
**user**: The user currently logged in. Can be the user object or `null` if login was skipped.
**roles**: (Optional) Roles related to the logged-in user.
**initialLoading**: (Optional) A flag used to avoid displaying the login screen when the app first loads and the login
status has not yet been determined.
**authLoading**: A flag used to display a loading screen while the user is logging in or out.
**signOut**: A method to sign out the user. Returns a `Promise`.
**authError**: (Optional) An error object representing issues initializing authentication.
**authProviderError**: (Optional) An error object dispatched by the authentication provider.
**getAuthToken**: A method to retrieve the authentication token for the current user. Returns a `Promise`.
**loginSkipped**: A flag indicating whether the user skipped the login process.
**extra**: An object containing additional data related to the authentication controller.
**setExtra**: A method to set the additional data for the authentication controller. Accepts `extra` parameter of
type `ExtraData`.
##### Additional Methods for `useFirebaseAuthController`
**googleLogin**: A method to initiate login using Google authentication.
**anonymousLogin**: A method to log in anonymously.
**appleLogin**: A method to initiate login using Apple authentication.
**facebookLogin**: A method to initiate login using Facebook authentication.
**githubLogin**: A method to initiate login using GitHub authentication.
**microsoftLogin**: A method to initiate login using Microsoft authentication.
**twitterLogin**: A method to initiate login using Twitter authentication.
**emailPasswordLogin**: A method to log in using an email and password. Takes `email` and `password` as parameters.
**fetchSignInMethodsForEmail**: A method to fetch sign-in methods for a given email. Takes `email` as a parameter and returns a `Promise`.
**createUserWithEmailAndPassword**: A method to create a new user using an email and password. Takes `email` and `password` as parameters.
**sendPasswordResetEmail**: A method to send a password reset email. Takes `email` as a parameter and returns a `Promise`.
**phoneLogin**: A method to log in using a phone number. Takes `phone` and `applicationVerifier` as parameters.
**confirmationResult**: (Optional) An object containing the result of a phone number authentication operation.
**skipLogin**: A method to skip the login process.
**setUser**: A method to set the user object. Takes `user` of type `FirebaseUser` or `null` as a parameter.
**setRoles**: A method to set roles for the logged-in user. Takes an array of `Role` objects as a parameter.
## Security Best Practices for Self-Hosted FireCMS
FireCMS is a **frontend-only React application**. It has no built-in server component that enforces security. This means that **all security must be implemented and enforced on your backend**. FireCMS client-side permissions (the `permissions` callback on collections) control the UI/UX — they hide buttons and disable forms — but they can be bypassed by any user with access to browser developer tools.
This guide covers everything you need to know to secure your self-hosted FireCMS deployment when using **MongoDB**, **custom authentication**, and **custom file storage** — without Firebase.
:::caution[Golden Rule]
**Never trust the client.** Every operation that reads, writes, or deletes data must be validated and authorized on your server. Client-side checks are for user experience only.
:::
---
### Architecture Overview
A secure self-hosted FireCMS deployment has the following architecture:
```
┌─────────────────┐ HTTPS ┌──────────────────┐
│ FireCMS React │ ──────────────────▶ │ Your API Server │
│ (Browser) │ ◀────────────────── │ (Express/Nest) │
└─────────────────┘ └──────┬───────────┘
│
┌──────────────────────┤
│ │
┌──────▼──────┐ ┌───────▼──────┐
│ MongoDB │ │ File Storage │
│ (Database) │ │ (S3/Minio) │
└─────────────┘ └──────────────┘
```
Key points:
- The browser **never** talks directly to MongoDB or your storage backend.
- Your API server is the single entry point that authenticates every request, authorizes the action, validates inputs, and then interacts with the database and storage.
---
### 1. Implementing a Secure AuthController
The `AuthController` interface manages the user's authentication state in the browser. When using a custom backend, your implementation should be a React hook that communicates with your own API.
#### Interface Recap
```typescript
type AuthController = {
user: USER | null;
initialLoading?: boolean;
authLoading: boolean;
signOut: () => Promise;
authError?: any;
getAuthToken: () => Promise;
loginSkipped: boolean;
extra: any;
setExtra: (extra: any) => void;
};
```
#### Best Practices
##### Token Management (`getAuthToken`)
Your `getAuthToken` implementation is the cornerstone of the entire security model — every request your `DataSourceDelegate` and `StorageSource` make will call it to attach credentials.
```typescript
interface CustomUser extends User {
uid: string;
displayName: string | null;
email: string | null;
photoURL: string | null;
providerId: string;
isAnonymous: boolean;
}
export function useCustomAuthController(): AuthController {
const [user, setUser] = useState(null);
const [authLoading, setAuthLoading] = useState(false);
const [initialLoading, setInitialLoading] = useState(true);
const [accessToken, setAccessToken] = useState(null);
const [extra, setExtra] = useState(null);
// On mount, check for an existing session
useEffect(() => {
checkSession().finally(() => setInitialLoading(false));
}, []);
const checkSession = useCallback(async () => {
try {
const res = await fetch("/api/auth/me", { credentials: "include" });
if (res.ok) {
const data = await res.json();
setUser(data.user);
setAccessToken(data.accessToken);
}
} catch {
// No valid session
}
}, []);
const getAuthToken = useCallback(async (): Promise => {
if (!accessToken) throw new Error("Not authenticated");
// Optional: check expiry and refresh
// const decoded = decodeJwt(accessToken);
// if (decoded.exp * 1000 < Date.now()) { ... refresh ... }
return accessToken;
}, [accessToken]);
const signOut = useCallback(async () => {
await fetch("/api/auth/logout", {
method: "POST",
credentials: "include"
});
setUser(null);
setAccessToken(null);
}, []);
return {
user,
authLoading,
initialLoading,
signOut,
getAuthToken,
loginSkipped: false,
extra,
setExtra,
};
}
```
**Key Security Points:**
| Concern | Recommendation |
|---|---|
| **Token storage** | Store JWTs in `httpOnly`, `Secure`, `SameSite=Strict` cookies when possible. If you must use in-memory tokens, never store them in `localStorage` or `sessionStorage`. |
| **Token expiry** | Use short-lived access tokens (5–15 minutes) with a refresh token flow. |
| **Token refresh** | Implement transparent token refresh in `getAuthToken` before expiry. |
| **Session invalidation** | `signOut` must call your server to invalidate the refresh token / session. A client-side-only logout is not secure. |
| **Initial loading** | Use `initialLoading` to silently check for an existing session on app mount. |
##### Authenticator Function
The `Authenticator` callback allows you to control **which authenticated users can access FireCMS**. Use it to load the user's role from your database and attach it to the auth controller.
```typescript
const myAuthenticator: Authenticator = async ({
user,
authController,
dataSourceDelegate
}) => {
if (!user?.email) return false;
try {
// Fetch the user profile from your backend (not MongoDB directly!)
const users = await dataSourceDelegate.fetchCollection({
path: "cms_users",
filter: { email: ["==", user.email] }
});
if (users.length === 0) return false;
const profile = users[0].values;
authController.setExtra({ role: profile.role });
return true;
} catch (error) {
console.error("Authentication error:", error);
return false;
}
};
```
:::tip
This callback runs on the client. **Your API server must still independently verify that the requesting user has the claimed role** on every request. The `Authenticator` is for UI gating only.
:::
---
### 2. Securing Your DataSourceDelegate (MongoDB)
The `DataSourceDelegate` is the interface FireCMS uses to read and write data. When backed by MongoDB, your implementation should **proxy every call through your authenticated API server**.
#### Never Expose MongoDB to the Browser
This is the most critical rule. Do not use the MongoDB driver, Realm SDK, or any direct database connection in the browser.
```typescript
// ❌ DANGEROUS — direct MongoDB access from the browser
const client = new MongoClient("mongodb+srv://user:password@cluster...");
// ✅ CORRECT — proxy through your authenticated API
const response = await fetch("/api/data/products", {
headers: { Authorization: `Bearer ${await authController.getAuthToken()}` }
});
```
#### Example: Secure DataSourceDelegate
```typescript
export function useSecureMongoDelegate(
getAuthToken: () => Promise
): DataSourceDelegate {
async function authenticatedFetch(url: string, options: RequestInit = {}) {
const token = await getAuthToken();
const res = await fetch(url, {
...options,
headers: {
...options.headers,
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
}
});
if (res.status === 401 || res.status === 403) {
throw new Error("Unauthorized");
}
if (!res.ok) {
throw new Error(`API error: ${res.status}`);
}
return res.json();
}
return {
key: "secure-mongo",
initialised: true,
async fetchCollection>({
path, filter, limit, startAfter, orderBy, order, searchString
}: FetchCollectionDelegateProps): Promise[]> {
const params = new URLSearchParams();
if (limit) params.set("limit", String(limit));
if (orderBy) params.set("orderBy", orderBy);
if (order) params.set("order", order);
if (searchString) params.set("q", searchString);
if (filter) params.set("filter", JSON.stringify(filter));
if (startAfter) params.set("startAfter", JSON.stringify(startAfter));
return authenticatedFetch(`/api/data/${path}?${params}`);
},
async fetchEntity>({
path, entityId
}: FetchEntityProps): Promise | undefined> {
return authenticatedFetch(`/api/data/${path}/${entityId}`);
},
async saveEntity>({
path, entityId, values, status
}: SaveEntityDelegateProps): Promise> {
const method = status === "new" ? "POST" : "PUT";
const url = entityId
? `/api/data/${path}/${entityId}`
: `/api/data/${path}`;
return authenticatedFetch(url, {
method,
body: JSON.stringify({ values })
});
},
async deleteEntity>({
entity
}: DeleteEntityProps): Promise {
await authenticatedFetch(`/api/data/${entity.path}/${entity.id}`, {
method: "DELETE"
});
},
async checkUniqueField(
path: string, name: string, value: any, entityId?: string
): Promise {
const result = await authenticatedFetch(
`/api/data/${path}/check-unique`,
{
method: "POST",
body: JSON.stringify({ field: name, value, entityId })
}
);
return result.unique;
},
generateEntityId(): string {
// Generate a client-side ID; the server should validate / regenerate
return crypto.randomUUID();
},
delegateToCMSModel: (data: any) => data,
cmsToDelegateModel: (data: any) => data,
};
}
```
#### Server-Side Security Checklist
Your API server (e.g. Express, Fastify, NestJS) must enforce the following on **every** request:
##### Authentication
```typescript
// Express middleware example
function authenticate(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Missing token" });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
return res.status(401).json({ error: "Invalid token" });
}
}
```
##### Authorization
```typescript
// Check the user's role before executing any CRUD operation
function authorize(requiredRole: string) {
return (req, res, next) => {
const user = req.user;
if (!user) return res.status(401).json({ error: "Not authenticated" });
// Look up the user's role in your database — don't trust the token's role claim alone
// unless the token is server-signed and verified above
if (user.role !== "admin" && user.role !== requiredRole) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
}
app.delete("/api/data/:path/:id", authenticate, authorize("admin"), async (req, res) => {
// Only admins can delete
});
```
##### Input Validation & NoSQL Injection Prevention
MongoDB is vulnerable to NoSQL injection when user input is passed directly to query operators.
```typescript
// ❌ VULNERABLE — user input goes directly into the query
app.get("/api/data/:collection", async (req, res) => {
const filter = JSON.parse(req.query.filter); // attacker can inject {$gt: ""}
const docs = await db.collection(req.params.collection).find(filter).toArray();
res.json(docs);
});
// ✅ SECURE — sanitize and whitelist
app.get("/api/data/:collection", authenticate, async (req, res) => {
// 1. Whitelist allowed collections
const allowedCollections = ["products", "orders", "categories"];
if (!allowedCollections.includes(req.params.collection)) {
return res.status(400).json({ error: "Invalid collection" });
}
// 2. Sanitize the filter to remove any MongoDB operators
let filter = {};
if (req.query.filter) {
filter = mongo.sanitize(JSON.parse(req.query.filter));
}
// 3. Enforce limits
const limit = Math.min(parseInt(req.query.limit) || 25, 100);
const docs = await db
.collection(req.params.collection)
.find(filter)
.limit(limit)
.toArray();
res.json(docs);
});
```
**Key Validations:**
| Check | Why |
|---|---|
| **Whitelist collections** | Prevent access to system collections (`admin`, `local`) or internal collections |
| **Sanitize filter operators** | Block `$where`, `$gt`, `$regex`, and other operators that can be injected |
| **Limit result size** | Prevent denial-of-service via unbounded queries |
| **Validate `orderBy` fields** | Only allow sorting on indexed/known fields |
| **Validate `entityId` format** | Ensure IDs match expected format (e.g. UUID or ObjectId pattern) |
| **Validate `values` on save** | Run schema validation (e.g. Zod, Joi) on the server before writing |
---
### 3. Securing Your StorageSource
The `StorageSource` interface handles file uploads and downloads. When using custom storage (S3, MinIO, GCS, or a local filesystem), the key principle is: **never expose storage credentials to the browser**.
#### Interface Recap
```typescript
interface StorageSource {
uploadFile: (props: UploadFileProps) => Promise;
getDownloadURL: (pathOrUrl: string, bucket?: string) => Promise;
getFile: (path: string, bucket?: string) => Promise;
deleteFile: (path: string, bucket?: string) => Promise;
list: (path: string, options?: { ... }) => Promise;
}
```
#### Use Pre-Signed URLs for Uploads
Instead of passing S3/GCS credentials to the browser, have your server generate short-lived pre-signed URLs:
```typescript
export function useSecureStorageSource(
getAuthToken: () => Promise
): StorageSource {
async function authenticatedFetch(url: string, options: RequestInit = {}) {
const token = await getAuthToken();
return fetch(url, {
...options,
headers: { ...options.headers, Authorization: `Bearer ${token}` }
});
}
return {
async uploadFile({ file, fileName, path }: UploadFileProps): Promise {
const usedFileName = fileName ?? file.name;
const destinationPath = `${path}/${usedFileName}`;
// 1. Get a pre-signed upload URL from your server
const res = await authenticatedFetch("/api/storage/upload-url", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
path: destinationPath,
contentType: file.type,
size: file.size
})
});
const { uploadUrl, storageUrl } = await res.json();
// 2. Upload directly to storage using the pre-signed URL
await fetch(uploadUrl, {
method: "PUT",
body: file,
headers: { "Content-Type": file.type }
});
return {
path: destinationPath,
bucket: "your-bucket",
storageUrl
};
},
async getDownloadURL(pathOrUrl: string): Promise {
const res = await authenticatedFetch(
`/api/storage/download-url?path=${encodeURIComponent(pathOrUrl)}`
);
if (!res.ok) return { url: null, fileNotFound: true };
const data = await res.json();
return { url: data.url, metadata: data.metadata };
},
async getFile(path: string): Promise {
const { url } = await this.getDownloadURL(path);
if (!url) return null;
const res = await fetch(url);
const blob = await res.blob();
return new File([blob], path.split("/").pop() || "file");
},
async deleteFile(path: string): Promise {
await authenticatedFetch(`/api/storage/files?path=${encodeURIComponent(path)}`, {
method: "DELETE"
});
},
async list(path: string, options?: { maxResults?: number; pageToken?: string }) {
const params = new URLSearchParams({ path });
if (options?.maxResults) params.set("maxResults", String(options.maxResults));
if (options?.pageToken) params.set("pageToken", options.pageToken);
const res = await authenticatedFetch(`/api/storage/list?${params}`);
return res.json();
}
};
}
```
#### Server-Side Storage Security
Your storage API endpoint must enforce:
| Concern | Recommendation |
|---|---|
| **File type validation** | Whitelist allowed MIME types (e.g. `image/jpeg`, `application/pdf`). Reject executables. |
| **File size limits** | Enforce maximum file sizes (e.g. 10 MB for images, 50 MB for documents). |
| **Path traversal prevention** | Sanitize the `path` parameter. Reject `..`, absolute paths, or null bytes. Never let clients dictate where files are stored without validation. |
| **Pre-signed URL expiry** | Keep upload/download URLs short-lived (5–15 minutes). |
| **Virus scanning** | For user-uploaded content, consider integrating ClamAV or a cloud-based scanning service. |
| **Access scoping** | Each pre-signed URL should grant access to exactly one file. Use the user's identity to scope which paths they can access. |
##### Server-Side Path Validation Example
```typescript
function validateStoragePath(path: string): boolean {
// Block path traversal
if (path.includes("..") || path.startsWith("/")) return false;
// Block null bytes
if (path.includes("\0")) return false;
// Whitelist allowed path prefixes
const allowedPrefixes = ["uploads/", "images/", "documents/"];
return allowedPrefixes.some(prefix => path.startsWith(prefix));
}
function validateFileType(contentType: string): boolean {
const allowedTypes = [
"image/jpeg", "image/png", "image/gif", "image/webp",
"application/pdf",
"video/mp4"
];
return allowedTypes.includes(contentType);
}
app.post("/api/storage/upload-url", authenticate, (req, res) => {
const { path, contentType, size } = req.body;
if (!validateStoragePath(path)) {
return res.status(400).json({ error: "Invalid path" });
}
if (!validateFileType(contentType)) {
return res.status(400).json({ error: "File type not allowed" });
}
if (size > 10 * 1024 * 1024) { // 10 MB
return res.status(400).json({ error: "File too large" });
}
// Generate and return pre-signed URL...
});
```
---
### 4. Permissions and Role-Based Access Control
FireCMS has a built-in `Permissions` system and a `PermissionsBuilder` callback that you can use on each collection. **These are UI-level controls** — they determine which buttons are shown and which forms are editable.
#### Client-Side Permissions (UI Only)
```typescript
export const productsCollection = buildCollection({
name: "Products",
path: "products",
permissions: ({ authController }) => {
const role = authController.extra?.role;
return {
read: true,
create: role === "admin" || role === "editor",
edit: role === "admin" || role === "editor",
delete: role === "admin"
};
},
properties: {
// ...
}
});
```
#### Mirror Permissions on the Server
Your API must enforce the **exact same rules**:
```typescript
// permissions.ts — shared logic (or replicated server-side)
type Role = "admin" | "editor" | "viewer";
const collectionPermissions: Record> = {
products: {
admin: { read: true, create: true, edit: true, delete: true },
editor: { read: true, create: true, edit: true, delete: false },
viewer: { read: true, create: false, edit: false, delete: false },
},
// ... other collections
};
function checkPermission(
collection: string,
action: "read" | "create" | "edit" | "delete",
role: Role
): boolean {
return collectionPermissions[collection]?.[role]?.[action] ?? false;
}
// Express middleware
function requirePermission(action: "read" | "create" | "edit" | "delete") {
return (req, res, next) => {
const collection = req.params.path;
const role = req.user.role;
if (!checkPermission(collection, action, role)) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
}
app.get("/api/data/:path", authenticate, requirePermission("read"), handler);
app.post("/api/data/:path", authenticate, requirePermission("create"), handler);
app.put("/api/data/:path/:id", authenticate, requirePermission("edit"), handler);
app.delete("/api/data/:path/:id", authenticate, requirePermission("delete"), handler);
```
:::important
If a user can modify their own role in the database (e.g. setting `role: "admin"` on their own user document), your permission system is broken. **Always restrict writes to user/role collections to admin-only operations.**
:::
---
### 5. General Security Best Practices
#### Transport Security
- **Always use HTTPS** in production. Use TLS 1.2+ with strong cipher suites.
- Set `Strict-Transport-Security` (HSTS) headers.
- Redirect all HTTP traffic to HTTPS.
#### CORS Configuration
Configure CORS on your API server to allow only your FireCMS domain:
```typescript
app.use(cors({
origin: "https://your-admin-panel.example.com",
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"]
}));
```
:::caution
**Never use `origin: "*"`** in production, especially when `credentials: true` is set. This allows any website to make authenticated requests to your API.
:::
#### Rate Limiting
Protect your API from brute-force attacks and abuse:
```typescript
// General API rate limit
app.use("/api/", rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 500
}));
// Stricter limit for auth endpoints
app.use("/api/auth/", rateLimit({
windowMs: 15 * 60 * 1000,
max: 20
}));
```
#### Content Security Policy
Set CSP headers to prevent XSS attacks:
```
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' https://your-storage.example.com;
connect-src 'self' https://your-api.example.com;
```
#### Secrets Management
| ❌ Don't | ✅ Do |
|---|---|
| Hardcode API keys in frontend code | Use environment variables on the server |
| Commit `.env` files to Git | Use a secrets manager (Vault, AWS Secrets Manager, Doppler) |
| Share MongoDB connection strings with the client | Keep all database connections server-side only |
| Use the same JWT secret across environments | Use unique secrets per environment |
#### MongoDB-Specific Security
- **Enable authentication** on your MongoDB cluster. Never run without auth.
- **Use a dedicated database user** for your API with the minimum required permissions.
- **Enable TLS** for connections between your API and MongoDB.
- **Network access control**: restrict which IPs can connect to your MongoDB cluster.
- **Enable audit logging** if your MongoDB plan supports it.
#### Dependency Security
- Run `npm audit` regularly and address vulnerabilities.
- Pin major dependency versions in `package.json`.
- Use `npm audit fix` or tools like [Snyk](https://snyk.io) or [Socket](https://socket.dev) for continuous monitoring.
---
### Summary Checklist
| Area | Requirement | Status |
|---|---|---|
| **Auth** | JWT/session tokens are server-signed and validated | ☐ |
| **Auth** | Tokens are short-lived with refresh flow | ☐ |
| **Auth** | `signOut` invalidates server-side session | ☐ |
| **Auth** | Tokens stored in httpOnly cookies (preferred) | ☐ |
| **Data** | All CRUD goes through authenticated API | ☐ |
| **Data** | MongoDB never exposed to browser | ☐ |
| **Data** | Server validates and sanitizes all inputs | ☐ |
| **Data** | NoSQL injection prevention in place | ☐ |
| **Data** | Collection access is whitelisted | ☐ |
| **Data** | Result size limits enforced | ☐ |
| **Storage** | Pre-signed URLs used for uploads/downloads | ☐ |
| **Storage** | File type and size validated server-side | ☐ |
| **Storage** | Path traversal prevented | ☐ |
| **Permissions** | Client-side permissions mirror server-side rules | ☐ |
| **Permissions** | Role assignment restricted to admins | ☐ |
| **General** | HTTPS enforced | ☐ |
| **General** | CORS restricted to CMS domain | ☐ |
| **General** | Rate limiting on all API endpoints | ☐ |
| **General** | CSP headers configured | ☐ |
| **General** | No secrets in frontend code | ☐ |
| **General** | MongoDB auth and TLS enabled | ☐ |
| **General** | Dependencies audited regularly | ☐ |
## Migrating from v3.1 to v3.2
This migration guide applies to **self-hosted versions** of FireCMS, including both **Community** and **PRO** editions.
FireCMS v3.2 brings a complete rewrite of the text editor, comprehensive internationalization support, and several important improvements.
### Before You Start
- Ensure you are currently on **FireCMS v3.1.x**
- Back up your project or commit your current state to version control
- Ensure you are using **Node.js 18+**
### Update FireCMS Packages
Update all `@firecms/*` packages to version **3.2**:
```bash
npm i @firecms/core@3.2.0 @firecms/ui@3.2.0 @firecms/firebase@3.2.0 @firecms/collection_editor@3.2.0 @firecms/collection_editor_firebase@3.2.0 @firecms/data_enhancement@3.2.0 @firecms/data_export@3.2.0 @firecms/data_import@3.2.0 @firecms/datatalk@3.2.0 @firecms/schema_inference@3.2.0 @firecms/user_management@3.2.0
```
Make sure to include **all** `@firecms/*` packages listed in your `package.json`. Mixing package versions will cause runtime errors.
---
### Editor Package Consolidation (Breaking Change)
The standalone `@firecms/editor` package has been deprecated and removed. All editor components have been migrated directly into `@firecms/core`.
:::caution
This is a **breaking change**. You must update your dependencies and imports.
:::
#### 1. Remove the old package
Remove `@firecms/editor` from your project:
```bash
npm uninstall @firecms/editor
```
#### 2. Update imports
Update any imports in your code that previously referenced `@firecms/editor` to import from `@firecms/core` instead:
**Before (v3.1):**
```typescript
```
**After (v3.2):**
```typescript
```
---
### Rich Text Editor Rewrite
The rich text editor has been completely reimplemented using ProseMirror to provide a more robust and feature-rich editing experience.
#### Key Editor Features:
- **Table Support**: Full support for tables with markdown parsing, slash command insertion, and a dedicated table bubble UI.
- **Image Handling**: A new image bubble allows editing image alt/title attributes and offers enhanced upload capabilities.
- **Markdown Mode**: A new toggle allows you to seamlessly switch between rich text mode and raw markdown editing mode.
- **Improved Slash Commands**: The slash command menu (`/`) now features better HTML parsing and stability.
- **Better Pasting**: Significantly improved paste behavior for text and images.
---
### Internationalization (i18n)
FireCMS now features full i18next integration with comprehensive translation coverage across the entire platform.
#### Add the i18n Provider
To enable internationalization, you **must** wrap your application in the new `FireCMSi18nProvider` exported from `@firecms/core`. This provider is now required and provides the active locale and translations to all FireCMS components.
```tsx
// ...
return (
);
```
Supported languages out-of-the-box now include:
- English
- Portuguese
- German
- French
- Spanish
- Italian
- Hindi
Translations have been extended to cover project settings, subscription management, AppCheck, security rules, and text search features.
---
### URL-Synchronized Table Filters
Table filters and sorting are now **synchronized with URL parameters**.
When applying filters or sorting to a collection table, the URL automatically updates. This allows you to:
- Bookmark specific filtered views of your data.
- Share exact table states (including active filters and sort order) with your team via URLs.
- Navigate back and forth in your browser history while maintaining the correct table state.
---
### Other Features & Improvements
#### CLI Updates
- **Astro Template**: The FireCMS CLI now includes a new Astro template for creating Astro-based projects seamlessly.
#### Storage
- **Upload Progress**: Added upload progress indicators for file uploads, improving visibility for large media files.
#### Collection Management
- **Error Banner**: Introduced a dedicated `CollectionDataErrorBanner` that displays collection data loading errors and provides helpful Firestore index suggestions.
- **Tabs Component**: Added scrollable Tabs with scroll indicator icons for better UX on smaller screens or configurations with many tabs.
---
### Troubleshooting
#### Build errors related to `@firecms/editor`
Ensure you have completely removed the `@firecms/editor` package from your `node_modules` and `package.json`. You may need to delete `node_modules` and your lockfile, then run `npm install` again. Check all files for leftover imports from `@firecms/editor`.
#### Type mismatch errors
Ensure **all** `@firecms/*` packages in your `package.json` are on exactly version `3.2.0`. Mixing v3.1 and v3.2 packages is the most common cause of build issues.
## Migrating from v3.0 to v3.1
This migration guide applies to **self-hosted versions** of FireCMS, including both **Community** and **PRO** editions.
FireCMS v3.1 introduces important updates that require configuration changes and offers exciting new features.
### Before You Start
- Ensure you are currently on **FireCMS v3.0.x**
- Back up your project or commit your current state to version control
- Ensure you are using **Node.js 18+**
### Update FireCMS Packages
Update all `@firecms/*` packages to version **3.1**:
```bash
npm i @firecms/core@3.1.0 @firecms/ui@3.1.0 @firecms/firebase@3.1.0 @firecms/collection_editor@3.1.0 @firecms/collection_editor_firebase@3.1.0 @firecms/data_enhancement@3.1.0 @firecms/data_export@3.1.0 @firecms/data_import@3.1.0 @firecms/schema_inference@3.1.0 @firecms/user_management@3.1.0
```
Make sure to include **all** `@firecms/*` packages listed in your `package.json`. Mixing v3.0 and v3.1 packages will cause issues.
---
### TailwindCSS v4 Migration
The most significant change in v3.1 is the migration from **TailwindCSS v3 to v4**. This involves updating your
TailwindCSS configuration and ensuring that your styles are compatible with the new version.
:::caution
This is a **breaking change**. Your project will not work correctly until you complete all steps in this section.
:::
#### Update Dependencies
Install TailwindCSS v4 and the Vite plugin:
```bash
npm i tailwindcss@4 @tailwindcss/vite
```
#### Add the Vite Plugin
Update your `vite.config.ts` to include the new TailwindCSS v4 plugin:
```typescript
export default defineConfig({
esbuild: {
logOverride: { "this-is-undefined-in-esm": "silent" }
},
build: {
outDir: "./build",
target: "ESNEXT",
sourcemap: true
},
optimizeDeps: { include: ["react/jsx-runtime"] },
plugins: [
react({}),
tailwindcss()
]
})
```
#### Color Variable Changes
The CSS variable names have been updated to follow the TailwindCSS v4 standard.
Update your `index.css` file to reflect these changes:
**Before (v3.0):**
```css
:root {
--fcms-primary: #0070F4;
--fcms-primary-dark: #0061e6;
--fcms-primary-bg: #0061e610;
--fcms-secondary: #FF5B79;
}
```
**After (v3.1):**
```css
:root {
--color-primary: #0070F4;
--color-secondary: #FF5B79;
}
```
:::warning
If you skip this step, your custom theme colors will **not be applied** and FireCMS will fall back to its default palette.
The `--fcms-primary-dark` and `--fcms-primary-bg` variables are no longer needed — they are computed automatically from `--color-primary`.
:::
#### Updated index.css Structure
Your `index.css` file should now look like this:
```css
@import "tailwindcss";
@import "@firecms/ui/index.css";
@source "../index.html";
@source "./**/*.{js,ts,jsx,tsx}";
@source "../node_modules/@firecms/**/*.{js,ts,jsx,tsx}";
@custom-variant dark (:is(.dark &));
:root {
--color-primary: #0070F4;
--color-secondary: #FF5B79;
}
body {
@apply w-full min-h-screen bg-gray-50 dark:bg-gray-900 flex flex-col items-center justify-center;
}
```
#### Clean Up Old Config Files
Remove the following files from your project as they are no longer needed:
- `tailwind.config.js`
- `postcss.config.js`
#### Verify Your Migration
After completing all steps, run:
```bash
npm run build
```
The build should complete without errors. If you see CSS-related warnings, double-check that your `index.css` matches the structure above.
---
### New Collection View Modes
FireCMS v3.1 introduces new ways to visualize your collection data beyond the traditional table view.
#### Cards View
The **Cards view** displays your entities as visual cards, making it easier to browse content-heavy collections
such as blog posts, products, or media libraries. Each card shows a preview of the entity with its key fields.
#### Kanban/Board View
The **Kanban view** (also called Board view) allows you to organize entities into columns based on an enum property.
This is perfect for:
- Workflow management (e.g., Draft → Review → Published)
- Task tracking (e.g., Todo → In Progress → Done)
- Status-based organization
To use the Kanban view, your collection needs an **enum property** that defines the columns. You can:
- Drag and drop entities between columns to update their status
- Reorder columns by dragging their headers
- Configure which property to use for column grouping
Both views can be enabled in your collection configuration or switched at runtime using the view toggle in the collection toolbar.
---
### Bug Fixes and Improvements
FireCMS v3.1 includes numerous bug fixes and improvements:
#### Editor & UI
- Improved escape key behavior in editor slash command
- Enhanced suggestion menu behavior
- Small UI adjustments and visual update to dialogs
- Removed font-mono from map preview
- Editor migration for improved markdown editor performance
#### Collection Editor
- Added inline editing for property editing
- Fixes for collection editor property saving
- Consistent behavior for `editable` props in collections and properties
#### Form Handling
- Displaying pre-save errors in table view
- Improved error focus when saving forms with errors
- Debouncing on values change in Formex
- Changed how dirty values are persisted in local storage
#### Local Changes
- Added `enableLocalChangesBackup` to collections for controlling local copy of unsaved entities
- Local changes can now be applied manually
- Clearing unsaved changes indicator when feature is not enabled
:::note
`enableLocalChangesBackup` defaults to **true**, matching the previous behavior. Set it to `false` in your collection config if you want to disable local backup of unsaved changes.
:::
#### Storage & Images
- New image resizing capabilities
- Replaced internal compressing library with compressor.js
- Improved error message when Firebase Storage is not enabled
#### Data & Performance
- Fixed dates losing focus while typing
- Fixed select enum filters UI glitch
- Fixed full screen entity views with encoded characters in their ID
---
### AI Features for Self-Hosted (PRO)
FireCMS v3.1 brings AI-powered features to self-hosted PRO users that were previously only available in FireCMS Cloud.
#### AI Collection Generation
You can now use AI to generate and modify collections directly in the collection editor. The AI can create collection schemas from natural language descriptions, add or modify properties, and understand relationships with your existing collections.
Enable it by passing a `generateCollection` callback to the collection editor plugin:
```typescript
const authController = useFirebaseAuthController({ firebaseApp });
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController,
collectionInference: buildCollectionInference(firebaseApp),
getData: (path, parentPaths) => getFirestoreDataInPath(firebaseApp, path, parentPaths, 200),
generateCollection: buildCollectionGenerationCallback({
getAuthToken: authController.getAuthToken
})
});
```
The `buildCollectionGenerationCallback` helper uses the default FireCMS API endpoint. You can optionally provide a custom endpoint:
```typescript
generateCollection: buildCollectionGenerationCallback({
getAuthToken: authController.getAuthToken,
apiEndpoint: "https://your-custom-endpoint.com/collections/generate"
})
```
#### DataTalk AI Chat
DataTalk allows you to query and update your Firestore data using natural language. You can ask questions about your data, create charts and visualizations, and even modify entities through conversation.
Enable DataTalk in your self-hosted app:
```typescript
// Configure DataTalk - only enable when user is logged in
const userEmail = authController.user?.email;
const dataTalkConfig = useBuildDataTalkConfig({
enabled: Boolean(userEmail),
firebaseApp,
userSessionsPath: userEmail ? `__FIRECMS/config/users/${userEmail}/datatalk_sessions` : undefined,
getAuthToken: authController.getAuthToken,
loadSamplePrompts: true
// apiEndpoint defaults to https://api.firecms.co
});
// Add DataTalk as a view in your navigation
const navigationController = useBuildNavigationController({
collections,
views: [
...yourViews,
{
path: "datatalk/*",
name: "DataTalk",
group: "AI",
view:
}
],
// ... other config
});
// Wrap your app with the DataTalkProvider
return (
{/* your app */}
);
```
DataTalk automatically reads your collection schema from the navigation controller to understand your data structure and provide more accurate responses.
:::note
AI features require a valid FireCMS PRO license. The API endpoints authenticate using your Firebase auth token.
:::
---
### Troubleshooting
#### `Cannot find namespace 'JSX'`
If you see TypeScript errors like `error TS2503: Cannot find namespace 'JSX'`, update your return type annotations from `JSX.Element` to `React.ReactElement` or simply remove the explicit return type and let TypeScript infer it.
#### Styles not applying after migration
- Verify your `index.css` includes the `@source` directives for your project files and `node_modules/@firecms`
- Ensure you removed the old `tailwind.config.js` and `postcss.config.js`
- Clear your `node_modules` folder and reinstall: `rm -rf node_modules && npm install`
#### Build errors after updating packages
Ensure **all** `@firecms/*` packages are on the same version. Mixing v3.0 and v3.1 packages will cause type mismatches and runtime errors.
## Migrating from previous beta versions
### FireCMS PRO
If you are migrating from previous beta versions of FireCMS PRO, you will need to make some updates to your project.
The main components have changed theis composition. Instead of having a single `Scaffold` components with all the configuration,
you have additionally an `AppBar` and a `Drawer` component.
More information about the main components can be found in the [Main Components](/docs/self/main_components) section.
#### User management auth controller
For self-hosted versions, there has been a change in the API for the data management controllers. The
`authController` is now passed to the User Management controller, instead of the other way around. The `userManagementController`
can be used as an auth controller, but with all the added logic for user management.
❌ Code before:
```typescript
/**
* Controller in charge of user management
*/
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate
});
/**
* Controller for managing authentication
*/
const authController: FirebaseAuthController = useFirebaseAuthController({
firebaseApp,
signInOptions,
loading: userManagement.loading,
defineRolesFor: userManagement.defineRolesFor
});
```
✅ Code after:
```typescript
/**
* Controller for managing authentication
*/
const authController: FirebaseAuthController = useFirebaseAuthController({
firebaseApp,
signInOptions
});
/**
* Controller in charge of user management
*/
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate,
authController
});
```
The `userManagement` controller now also qualifies as an `authController`, so you can use it as such, but it is not
necessary to do so.
#### Styling
You also will need to import the default FireCMS styles in your project. You can do this by adding the following import to your `index.css` file:
```css
@import "@firecms/ui/index.css";
```
Your `index.css` file should look like this:
```css
@import "@firecms/ui/index.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-primary: #0070F4;
--color-secondary: #FF5B79;
}
```
#### Dependencies
The default fonts are now imported in the clients project (so they can be replaced if needed).
You need to add these imports:
```
"typeface-rubik": "^1.1.13",
"@fontsource/jetbrains-mono": "^5.0.20",
```
### FireCMS Cloud
If you are migrating from previous beta versions of FireCMS Cloud, you will need to make some updates to your project.
#### Dependencies
The main package has been renamed from `firecms` to `@firecms/cloud` since version `3.0.0-beta.4`.
You can also remove the `@firecms/cli` package, as it is implicitly installed by `@firecms/cloud`.
This is a sample `package.json` with the new config:
```
{
"name": "my-firecms-project",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --port 5001",
"build": "vite build",
"serve": "vite preview --port 5001",
"deploy": "run-s build && firecms deploy --project=YOUR_PROJECT_ID"
},
"dependencies": {
"@firecms/cloud": "^3.0.0-beta",
"firebase": "^12.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@originjs/vite-plugin-federation": "^1.4.1",
"@tailwindcss/typography": "^0.5.10",
"@types/react": "^18.2.71",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.3",
"vite": "^5.2.6"
}
}
```
#### Imports
You need to update your imports to use the new package name. For example, if you have a file that imports `firecms`:
```javascript
```
You need to update it to use the new package name:
```javascript
```
#### Vite configuration
You also need to update your `vite.config.js` file to include the new package name and config:
```javascript
// https://vitejs.dev/config/
export default defineConfig({
esbuild: {
logOverride: { "this-is-undefined-in-esm": "silent" }
},
plugins: [
react(),
federation({
name: "remote_app",
filename: "remoteEntry.js",
exposes: {
"./config": "./src/index"
},
shared: [
"react",
"react-dom",
"@firecms/cloud",
"@firecms/core",
"@firecms/firebase",
"@firecms/ui",
"@firebase/firestore",
"@firebase/app",
"@firebase/functions",
"@firebase/auth",
"@firebase/storage",
"@firebase/analytics",
"@firebase/remote-config",
"@firebase/app-check"
]
})
],
build: {
modulePreload: false,
target: "ESNEXT",
cssCodeSplit: false,
}
})
```
#### Tailwind CSS
- You need to add the tailwind typography plugin to your project. You can do this by installing the `@tailwindcss/typography` package:
```bash
yarn add -D @tailwindcss/typography
```
- You should also make sure you are using at least version `3.4.3` of the `tailwindcss` package, and `postcss` version `8.4.38`.
- The preset now comes from the `@firecms/ui` package. You can update your `tailwind.config.cjs` file to include the new preset:
```javascript
import fireCMSConfig from "@firecms/ui/tailwind.config.js";
export default {
presets: [fireCMSConfig],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@firecms/**/src/**/*.{js,ts,jsx,tsx}",
],
};
```
## Migrating from FireCMS 2.0 to FireCMS 3.0
FireCMS 3.0 is a major release that introduces a lot of changes. This page
describes the main changes and how to migrate from FireCMS 2.0.
FireCMS Community and PRO are the self-hosted versions of FireCMS. It allows you to host your own backend and use FireCMS without any restrictions.
It is the most similar version to FireCMS 2.0, but with a lot of improvements and new features.
### Migrating to FireCMS PRO
Most of the concepts are the same as in FireCMS 2.0, but there are some changes that you need to be aware of.
We recommend starting a new project with:
```bash
npx create-firecms-app --pro
```
or
```bash
yarn create firecms-app --pro
```
This will create a new project with the latest version of FireCMS PRO.
### Updating the imports
The main change is that the imports have changed. You need to update the imports in your project.
Before you would import everything from `firecms` (or even `@camberi/firecms`). Now you need to import from
different packages.
- All UI components are now in `@firecms/ui`. Everything including buttons, textfields, layouts, etc.
- The core of FireCMS is in `@firecms/core`. This includes the `FireCMSApp`, `FireCMSContext`, etc.
- All Firebase related code is in `@firecms/firebase`, including `useFirebaseAuthController`, `use` etc.
Most of the imports can be found in `@firecms/core`, so we recommend starting there.
### Collection configuration
Collections have suffered minimal changes. If you don't have any custom components defined, it should be
easy to adapt your collections to the new format.
- You need to define an `id` for each collection, which typically can be the same as the `path`. Make sure the
`id` is unique.
- The prop `views` has been renamed to `entityViews`, since they are applied to entities.
- Within `entityViews` the prop `path` has been renamed to `key`.
- For `AdditionalFieldDelegate`:
- The prop `id` has been renamed to `key`.
- The prop `builder` has been renamed to `Builder`.
### Authenticator
- The authenticator now returns a `dataSourceDelegate` instead of a `dataSource`. The difference is that you do
not pass the `collection` prop anymore.
### Other
- `useNavigationContext` has been renamed to `useNavigationController`.
- `FieldDescription` has been renamed to `FieldCaption`.
- `PropertyPreview` no longer need an `entity` prop.
### Migrating custom components (MUI)
FireCMS 3.0 is based on `tailwindcss` instead of `mui`.
Mui was great for the initial versions of FireCMS, but it was being a big performance bottleneck
and it was hard to customize.
The new version of FireCMS has built in almost 50 new components implemented with tailwindcss, that
mimic in a good way the material-ui components. You are encouraged to migrate your custom components
to the new format.
You can try replacing imports from `@mui/material` to `@firecms/ui` and will see that many things work out of the box.
#### Icons
Icons in FireCMS are based on the material icons. You can use all the material icons importing them just like in MUI.
```tsx
```
The prop `fontSize` is called `size` in FireCMS (because it just makes more sense, MUI).
#### Components that have no equivalent:
- `Box`: The box component is just a wrapper used by mui to apply styles. You can use a `div` instead, with some
tailwind classes.
Tip: ChatGPT is great at converting Box components to div with tailwind classes.
- `Link`: Use `a` instead.
- `FormControl`
#### Components that change behaviour (from MUI to FireCMS UI)
- `Menu` and `MenuItem`: Menu items do not have an id anymore. You can add an `onClick` props per menu item.
- `Select` does not use `labelId` anymore. Just add the label as a component in `label`.
- `SelectChangeEvent` is now `ChangeEvent`
- `CircularProgress` size is a string instead of a number. You can use `size="small"` or `size="large"`.
#### Continue using MUI
```bash
npm install @mui/material @emotion/react @emotion/styled
However, if you want to keep using mui: you can still use the old components, but you will need to
or
```bash
install the `mui` package manually.
```
yarn add @mui/material @emotion/react @emotion/styled
```
```bash
npm install @mui/icons-material
or
```bash
If you need MUI icons, run:
```
yarn add @mui/icons-material
```
## Collections
**Collections** are the core building blocks of your FireCMS **admin panel**. They define how your **Firestore data** is displayed, edited, and managed in the CMS interface.
If you're building a **headless CMS** or **back-office** for your **Firebase** project, collections are where you define:
- **What data** users can manage (products, users, articles, orders, etc.)
- **How that data looks** in forms and tables (field types, validation, layout)
- **Who can do what** (create, read, update, delete permissions)
- **Custom logic** (callbacks on save, computed fields, side effects)
:::tip[Why use FireCMS collections?]
Unlike traditional CMSs that impose a rigid data model, FireCMS collections map directly to your existing **Firestore** structure. This means you can add a powerful **React-based admin UI** to any Firebase project without migrating your data or changing your schema.
:::
Collections appear at the **top level** of the navigation (home page and drawer), or as **subcollections** nested under parent entities.
You can define collections in two ways:
- **No-code**: Use the built-in **Collection Editor UI** (requires appropriate permissions)
- **Code-first**: Define collections programmatically with full **TypeScript** support and access to all advanced features (callbacks, custom fields, computed properties)
### Defining your collections
You can create your collections **in the UI or using code**. You can also mix both approaches, but keep in mind that
collections defined in the UI will take precedence. For example, you might have an enum property with 2 values defined
in code, and one extra value defined in the UI. When merged, the resulting enum will have 3 values.
:::important
You can have the same collection defined in both ways. In that case, the collection defined in the UI will
take precedence.
A deep merge is performed, so you can define some properties in the code, and override them in the UI. For example, you
can define an enum string property and the values will be merged from both definitions.
:::
#### Sample collection defined in code
:::note
FireCMS provides around 20 different fields (such as text fields, selects, and complex ones like reference or
sortable array fields). If your use case is not covered by one of the provided fields, you can create your
own [custom field](https://firecms.co/docs/properties/custom_fields.mdx).
:::
:::tip
You don't need to use `buildCollection` or `buildProperty` for building the configuration. They are identity
functions that will help you detect type and configuration errors
:::
```tsx
type Product = {
name: string;
main_image: string;
available: boolean;
price: number;
related_products: EntityReference[];
publisher: {
name: string;
external_id: string;
}
}
const productsCollection = buildCollection({
id: "products",
path: "products",
name: "Products",
group: "Main",
description: "List of the products currently sold in our shop",
textSearchEnabled: true,
openEntityMode: "side_panel",
properties: {
name: buildProperty({
dataType: "string",
name: "Name",
validation: { required: true }
}),
main_image: buildProperty({
dataType: "string",
name: "Image",
storage: {
mediaType: "image",
storagePath: "images",
acceptedFiles: ["image/*"],
metadata: {
cacheControl: "max-age=1000000"
}
},
description: "Upload field for images",
validation: {
required: true
}
}),
available: buildProperty({
dataType: "boolean",
name: "Available",
columnWidth: 100
}),
price: buildProperty(({ values }) => ({
dataType: "number",
name: "Price",
validation: {
requiredMessage: "You must set a price between 0 and 1000",
min: 0,
max: 1000
},
disabled: !values.available && {
clearOnDisabled: true,
disabledMessage: "You can only set the price on available items"
},
description: "Price with range validation"
})),
related_products: buildProperty({
dataType: "array",
name: "Related products",
description: "Reference to self",
of: {
dataType: "reference",
path: "products"
}
}),
publisher: buildProperty({
name: "Publisher",
description: "This is an example of a map property",
dataType: "map",
properties: {
name: {
name: "Name",
dataType: "string"
},
external_id: {
name: "External id",
dataType: "string"
}
}
})
},
permissions: ({
user,
authController
}) => ({
edit: true,
create: true,
delete: false
})
});
```
In FireCMS Cloud, this collection can then be used by including it in the `collections` prop of your main export,
a `FireCMSAppConfig`
object.
In FireCMS PRO, `collections` are passed directly to the `useBuildNavigationController` hook.
#### Modifying a collection defined in the UI
If you just need to add some code to a collection defined in the UI, you can use the `modifyCollection` function in
your `FireCMSAppConfig` object.
This applies to **FireCMS Cloud** only.
```tsx
const appConfig: FireCMSAppConfig = {
version: "1",
collections: async (props) => {
return ([
// ... full-code defined collections here
]);
},
modifyCollection: ({ collection }) => {
if (collection.id === "products") {
return {
...collection,
name: "Products modified",
entityActions: [
{
name: "Sample entity action",
onClick: ({ entity }) => {
console.log("Entity", entity);
}
}
]
}
}
return collection;
}
}
export default appConfig;
```
You can use all the props available in the `Collection` interface.
### Subcollections
Subcollections are collections of entities that are found under another entity. For example, you can have a collection
named "translations" under the entity "Article". You just need to use the same format as for defining your collection
using the field `subcollections`.
Subcollections are easily accessible from the side view while editing an entity.
### Filters
:::tip
If you need to have some filters and sorting applied by default, you can use the `initialFilter`and `initialSort`
prop. You can also force a filter combination to be always applied by using the `forceFilter`prop.
:::
Filtering is enabled by default for string, numbers, booleans, dates, and arrays. A dropdown is included in every
column of the collection where applicable.
Since Firestore has limited querying capabilities, each time you apply a filter or new sort, the previous sort/filter
combination gets reset by default (unless filtering, sorting by the same property).
If you need to enable filtering/sorting by more than one property at a time, you can specify the filters that you have
enabled in your Firestore configuration. In order to do so, just pass the indexes configuration to your collection:
```tsx
const productsCollection = buildCollection({
id: "products",
path: "products",
name: "Product",
properties: {
// ...
},
indexes: [
{
price: "asc",
available: "desc"
}
]
});
```
### Collection configuration
The `name` and `properties` you define for your entity collection will be used to generate the fields in the
spreadsheet-like collection tables, and the fields in the generated forms.
:::tip
You can force the CMS to always open the form when editing a document by setting the `inlineEditing` property
to `false` in the collection configuration.
:::
- **`name`**: The plural name of the collection. E.g., 'Products'.
- **`singularName`**: The singular name of an entry in the collection. E.g., 'Product'.
- **`path`**: Relative Firestore path of this view to its parent. If this view is in the root, the path is equal to the
absolute one. This path also determines the URL in FireCMS.
- **`properties`**: Object defining the properties for the entity schema. More information
in [Properties](https://firecms.co/docs/properties/properties_intro).
- **`propertiesOrder`**: Order in which the properties are displayed.
- For properties, use the property key.
- For additional field, use the field key.
- If you have subcollections, you get a column for each subcollection, with the path (or alias) as the
subcollection, prefixed with `subcollection:`. E.g., `subcollection:orders`.
- If you are using a collection group, you will also have an additional `collectionGroupParent` column.
- Note that if you set this prop, other ways to hide fields, like `hidden` in the property definition, will be
ignored. `propertiesOrder` has precedence over `hidden`.
```typescript
propertiesOrder: ["name", "price", "subcollection:orders"]
```
- **`openEntityMode`**: Determines how the entity view is opened. You can choose between `side_panel` (default) or
`full_screen`.
- **`formAutoSave`**: If set to true, the form will be auto-saved when the user changes the value of a field. Defaults
to false. You can't use this prop if you are using a `customId`.
- **`collectionGroup`**: If this collection is a top-level navigation entry, you can set this property to `true` to
indicate that this collection is a collection group.
- **`alias`**: You can set an alias that will be used internally instead of the `path`. The `alias` value will be used
to determine the URL of the collection while `path` will still be used in the datasource. Note that you can use this
value in reference properties too.
- **`icon`**: Icon key to use in this collection. You can use any of the icons in the Material
specs: [Material Icons](https://fonts.google.com/icons). e.g., 'account_tree' or 'person'.
Find all the icons in [Icons](https://firecms.co/docs/icons).
You can also pass your own icon component (`React.ReactNode`).
- **`customId`**: If this prop is not set, the ID of the document will be created by the datasource. You can set the
value to 'true' to force the users to choose the ID.
- **`subcollections`**: Following the Firestore document and collection schema, you can add subcollections to your
entity in the same way you define the root collections.
- **`defaultSize`**: Default size of the rendered collection.
- **`group`**: Optional field used to group top-level navigation entries under a navigation view. If you set this value
in a subcollection, it has no effect.
- **`description`**: Optional description of this view. You can use Markdown.
- **`entityActions`**: You can define additional actions that can be performed on the entities in this collection. These
actions can be displayed in the collection view or in the entity view. You can use the `onClick` method to implement
your own logic. In the `context` prop, you can access all the controllers of FireCMS.
You can also define entity actions globally. See [Entity Actions](https://firecms.co/docs/entity_actions) for more details.
```tsx
const archiveEntityAction: EntityAction = {
icon: ,
name: "Archive",
onClick({
entity,
collection,
context
}): Promise {
// Add your code here
return Promise.resolve(undefined);
}
}
```
- **`initialFilter`**: Initial filters applied to this collection. Defaults to none. Filters applied with this prop can
be changed by the user.
```tsx
initialFilter: {
age: [">=", 18]
}
```
```tsx
initialFilter: {
related_user: ["==", new EntityReference("sdc43dsw2", "users")]
}
```
- **`forceFilter`**: Force a filter in this view. If applied, the rest of the filters will be disabled. Filters applied
with this prop cannot be changed.
```tsx
forceFilter: {
age: [">=", 18]
}
```
```tsx
forceFilter: {
related_user: ["==", new EntityReference("sdc43dsw2", "users")]
}
```
- **`initialSort`**: Default sort applied to this collection. It takes tuples in the shape `["property_name", "asc"]`
or `["property_name", "desc"]`.
```tsx
initialSort: ["price", "asc"]
```
- **`Actions`**: Builder for rendering additional components such as buttons in the collection toolbar. The builder
takes an object with props `entityCollection` and `selectedEntities` if any are set by the end user.
- **`pagination`**: If enabled, content is loaded in batches. If `false` all entities in the
collection are loaded. This means that when reaching the end of the collection, the CMS will load more entities.
You can specify a number to specify the pagination size (50 by default)
Defaults to `true`
- **`additionalFields`**: You can add additional fields to both the collection view and the form view by implementing an
additional field delegate.
- **`textSearchEnabled`**: Flag to indicate if a search bar should be displayed on top of the collection table.
- **`permissions`**: You can specify an object with boolean permissions with the
shape `{edit:boolean; create:boolean; delete:boolean}` to indicate the actions the user can perform. You can also pass
a [`PermissionsBuilder`](https://firecms.co/docs/api/type-aliases/PermissionsBuilder) to customize the permissions based on the user or entity.
- **`inlineEditing`**: Can the elements in this collection be edited inline in the collection view? If this flag is set
to false but `permissions.edit` is `true`, entities can still be edited in the side panel.
- **`selectionEnabled`**: Are the entities in this collection selectable? Defaults to `true`.
- **`selectionController`**: Pass your own selection controller if you want to control selected entities
externally. [See `useSelectionController`](https://firecms.co/docs/api/functions/useSelectionController).
- **`exportable`**: Should the data in this collection view include an export button? You can also set
an [`ExportConfig`](https://firecms.co/docs/api/interfaces/ExportConfig) configuration object to customize the export and add additional
values. Defaults to `true`.
- **`hideFromNavigation`**: Should this collection be hidden from the main navigation panel if it is at the root level,
or in the entity side panel if it's a subcollection? It will still be accessible if you reach the specified path. You
can also use this collection as a reference target.
- **`callbacks`**: This interface defines all the callbacks that can be used when an entity is being created, updated,
or deleted. Useful for adding your own logic or blocking the operation's execution. [More information](https://firecms.co/docs/callbacks).
- **`entityViews`**: Array of builders for rendering additional panels in an entity view. Useful if you need to render custom
views for your entities. [More information](https://firecms.co/docs/collections/entity_views).
- **`alwaysApplyDefaultValues`**: If set to true, the default values of the properties will be applied
to the entity every time the entity is updated (not only when created).
Defaults to false.
- **`databaseId`**: Optional database id of this collection. If not specified, the default database id will be used.
Useful when working with multiple databases.
- **`previewProperties`**: Default preview properties displayed when this collection is referenced.
- **`titleProperty`**: Title property of the entity. This property will be used as the title in entity views and
references. If not specified, the first simple text property will be used.
- **`defaultSelectedView`**: If you want to open custom views or subcollections by default when opening an entity,
specify the path here. Can be a string or a builder function.
- **`hideIdFromForm`**: Should the ID of this collection be hidden from the form view.
- **`hideIdFromCollection`**: Should the ID of this collection be hidden from the grid view.
- **`sideDialogWidth`**: Width of the side dialog (in pixels or string) when opening an entity in this collection.
- **`editable`**: Can this collection configuration be edited by the end user. Defaults to `true`.
Has effect only if you are using the collection editor.
- **`includeJsonView`**: If set to true, a tab with the JSON representation of the entity will be included.
- **`history`**: If set to true, changes to the entity will be saved in a subcollection.
This prop has no effect if the history plugin is not enabled.
- **`localChangesBackup`**: Should local changes be backed up in local storage to prevent data loss.
Options: `"manual_apply"` (prompt to restore), `"auto_apply"` (automatically restore), or `false`. Defaults to `"manual_apply"`.
- **`defaultViewMode`**: Default view mode for displaying this collection.
Options: `"table"` (spreadsheet-like, default), `"cards"` (grid of cards with thumbnails), `"kanban"` (board grouped by property).
- **`kanban`**: Configuration for Kanban board view mode. Requires a `columnProperty` referencing an enum property.
When set, the Kanban view mode becomes available.
```tsx
kanban: {
columnProperty: "status" // Must reference a string property with enumValues
}
```
- **`orderProperty`**: Property key to use for ordering items. Must reference a number property.
When items are reordered, this property will be updated to reflect the new order using fractional indexing.
Used by Kanban view for ordering within columns.
- **`viewGroups`**: Group subcollections and custom views into dropdown menus in the entity view tabs.
Views listed in a group are removed from the top-level tabs and shown under a single dropdown instead.
[More information](https://firecms.co/docs/entity_views#grouping-views-and-subcollections).
```tsx
viewGroups: [
{
name: "Related data",
views: ["locales", "reviews", "preview"]
}
]
```
## Collection View Modes
FireCMS offers three different ways to visualize your collections. Each view mode is optimized for different types of data and workflows.

### Available View Modes
| View Mode | Description | Best For |
|-----------|-------------|----------|
| **Table** | Spreadsheet-like grid with inline editing | Dense data, bulk operations, detailed records |
| **Cards** | Responsive grid displaying thumbnails and key fields | Visual content, product catalogs, media libraries |
| **Kanban** | Board with columns based on a status/category field | Workflows, task management, order pipelines |
### Setting the Default View
Use the `defaultViewMode` property in your collection configuration:
```typescript
const productsCollection = buildCollection({
path: "products",
name: "Products",
defaultViewMode: "cards", // "table" | "cards" | "kanban"
properties: {
// ...
}
});
```
Users can still switch between views using the view selector in the collection toolbar — the `defaultViewMode` just sets what they see first.
---
### Restricting Available Views
By default, all three view modes are available. Use `enabledViews` to restrict which views appear in the selector:
```typescript
const ordersCollection = buildCollection({
path: "orders",
name: "Orders",
enabledViews: ["table", "kanban"], // Cards view won't be available
properties: {
// ...
}
});
```
:::note
The Kanban view is automatically available whenever your collection has at least one string property with `enumValues`. If no enum properties exist, Kanban will not appear in the selector even if included in `enabledViews`.
:::
---
### Table View
The default view mode. Displays entities in a spreadsheet-like grid with support for:
- Inline editing
- Sorting and filtering
- Column resizing and reordering
- Bulk selection
**Best for:** User lists, transaction logs, analytics data, any collection where you need to see many fields at once.
---
### Cards View
Transforms your collection into a responsive grid of cards. Each card displays:
- Image thumbnails (automatically detected from image properties)
- Title and key metadata
- Quick actions

#### Enable Cards View
```typescript
const productsCollection = buildCollection({
path: "products",
name: "Products",
defaultViewMode: "cards",
properties: {
name: buildProperty({ dataType: "string", name: "Name" }),
image: buildProperty({
dataType: "string",
storage: { mediaType: "image", storagePath: "products" }
}),
price: buildProperty({ dataType: "number", name: "Price" })
}
});
```
**Best for:** Product catalogs, blog posts, media libraries, team directories, portfolios — any collection with images.
---
### Kanban View
Displays entities as cards organized into columns based on an enum property. Drag and drop cards between columns to update their status.

#### Auto-Detection
The Kanban view is **automatically available** for any collection that has at least one string property with `enumValues` defined. No additional configuration is required — just define your enum property and the Board option will appear in the view selector.
#### Setting a Default Column Property
When your collection has multiple enum properties, you can set which one is used for columns by default with the `kanban` config. Users can switch between enum properties from the view selector.
```typescript
const tasksCollection = buildCollection({
path: "tasks",
name: "Tasks",
defaultViewMode: "kanban",
kanban: {
columnProperty: "status" // Optional: pre-selects which enum to group by
},
properties: {
title: buildProperty({ dataType: "string", name: "Task" }),
status: buildProperty({
dataType: "string",
name: "Status",
enumValues: {
todo: "To Do",
in_progress: "In Progress",
review: "Review",
done: "Done"
}
})
}
});
```
#### Drag and Drop Reordering
To enable reordering cards within a column, add an `orderProperty`:
```typescript
const tasksCollection = buildCollection({
path: "tasks",
name: "Tasks",
defaultViewMode: "kanban",
kanban: { columnProperty: "status" },
orderProperty: "order", // Must reference a number property
properties: {
title: buildProperty({ dataType: "string", name: "Task" }),
status: buildProperty({
dataType: "string",
name: "Status",
enumValues: { todo: "To Do", in_progress: "In Progress", done: "Done" }
}),
order: buildProperty({ dataType: "number", name: "Order" })
}
});
```
The `orderProperty` uses fractional indexing to maintain order without rewriting every document on each reorder.
:::caution[Firestore Index Required]
When using Kanban view with Firestore, you'll need a composite index on your column property. Firestore will prompt you with the exact index link when you first load the view.
:::
**Best for:** Task management, order fulfillment, content pipelines, support tickets, hiring workflows — any collection with distinct stages.
---
### Configuration in FireCMS Cloud
If you're using FireCMS Cloud, you can configure view modes through the UI without writing code:
1. Open your collection settings
2. Go to the **Display** tab
3. Select your **Default collection view** (Table, Cards, or Kanban)
4. For Kanban, choose the **Kanban Column Property** and optionally an **Order Property**

## Entity callbacks
When working with an entity, you can attach different callbacks before and
after it gets saved or fetched:
`onFetch`, `onIdUpdate`, `onPreSave`, `onSaveSuccess` and `onSaveFailure`.
These callbacks are defined at the collection level under the prop `callbacks`.
The `onIdUpdate` callback can be used to update the ID of the entity before
saving it. This is useful if you need to generate the ID from other fields.
This is useful if you need to add some logic or edit some fields or the entity
IF before/after saving or deleting entities.
Most callbacks are asynchronous.
:::note
You can stop the execution of these callbacks by throwing an `Error`
containing a `string` and an error snackbar will be displayed.
:::
:::tip
You can use the `context` object to access the FireCMS context.
The `context` object contains all the controllers and services available in the app,
including the `authController`, `dataSource`, `storageSource`, `sideDialogsController`, etc.
:::
```tsx
type Product = {
name: string;
uppercase_name: string;
}
const productCallbacks = buildEntityCallbacks({
onPreSave: ({
collection,
path,
entityId,
values,
previousValues,
status
}) => {
// return the updated values
values.uppercase_name = values.name?.toUpperCase();
return values;
},
onSaveSuccess: (props: EntityOnSaveProps) => {
console.log("onSaveSuccess", props);
},
onSaveFailure: (props: EntityOnSaveProps) => {
console.log("onSaveFailure", props);
},
onPreDelete: ({
collection,
path,
entityId,
entity,
context
}: EntityOnDeleteProps
) => {
if (!context.authController.user)
throw Error("Not logged in users cannot delete products");
},
onDelete: (props: EntityOnDeleteProps) => {
console.log("onDelete", props);
},
onFetch({
collection,
context,
entity,
path,
}: EntityOnFetchProps) {
entity.values.name = "Forced name";
return entity;
},
onIdUpdate({
collection,
context,
entityId,
path,
values
}: EntityIdUpdateProps): string {
// return the desired ID
return toSnakeCase(values?.name)
},
});
const productCollection = buildCollection({
name: "Product",
path: "products",
properties: {
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
uppercase_name: {
name: "Uppercase Name",
dataType: "string",
disabled: true,
description: "This field gets updated with a preSave callback"
}
},
callbacks: productCallbacks
});
```
##### EntityOnSaveProps
* `collection`: Resolved collection of the entity
* `path`: string Full path where this entity is being saved (may contain unresolved aliases)
* `resolvedPath`: string Full path with alias resolved
* `entityId`: string ID of the entity
* `values`: EntityValues Values being saved
* `previousValues`: EntityValues Previous values of the entity
* `status`: EntityStatus New or existing entity
* `context`: FireCMSContext Context of the app status
##### EntityOnDeleteProps
* `collection`: Resolved collection of the entity
* `path`: string Full path where this entity is being saved
* `entityId`: string ID of the entity
* `entity`: Entity Deleted entity
* `context`: FireCMSContext Context of the app status
##### EntityIdUpdateProps
* `collection`: EntityCollection Resolved collection of the entity
* `path`: string Full path where this entity is being saved
* `entityId`: string ID of the entity
* `values`: Entity values
* `context`: FireCMSContext Context of the app status
## Entity views

FireCMS offers default form and table fields for common use cases and also allows
overriding fields if you need a custom implementation, but that might be not
enough in certain cases, where you might want to have a full **custom view related
to one entity**.
Typical use cases for this are:
- **Preview** of an entity in a specific format.
- Checking how the data looks in a **web page**.
- Defining a **dashboard**.
- Modifying the state of the **form**.
- ... or any other custom view you might need.
When your entity view is defined you can add directly to the collection
or include it in the entity view registry.
#### Defining an entity custom view
In order to accomplish that you can pass an array of `EntityCustomView`
to your schema. Like in this example:
```tsx
const sampleView: EntityCustomView = {
key: "preview",
name: "Blog entry preview",
Builder: ({
collection,
entity,
modifiedValues,
formContext
}) => (
// This is a custom component that you can build as any React component
)
};
```
#### Building a secondary form

In your custom views, you can also add fields that are mapped directly to the entity.
This is useful if you want to add a secondary form to your entity view.
You can add any field, by using the `PropertyFieldBinding` component. This component
will bind the value to the entity, and it will be saved when the entity is saved.
In this example we creating a secondary form with a map field, including name and age:
```tsx
export function SecondaryForm({
formContext
}: EntityCustomViewParams) {
return (
);
}
```
Then just add your custom view to the collection:
```tsx
export const testCollection = buildCollection({
id: "users",
path: "users",
name: "Users",
properties: {
// ... your blog properties here
},
entityViews: [{
key: "user_details",
name: "Details",
includeActions: true, // this prop allows you to include the default actions in the bottom bar
Builder: SecondaryForm
}]
});
```
Note that you can use the `includeActions` prop to include the default actions in the bottom bar, of the view,
so the user doesn't need to go back to the main form view to perform actions like saving or deleting the entity.
#### Add your entity view directly to the collection
If you are editing a collection in code you can add your custom view
directly to the collection:
```tsx
const blogCollection = buildCollection({
id: "blog",
path: "blog",
name: "Blog",
entityViews: [
{
key: "preview",
name: "Blog entry preview",
Builder: ({
collection,
entity,
modifiedValues
}) => (
// This is a custom component that you can build as any React component
)
}
],
properties: {
// ... your blog properties here
}
});
```
#### Add your entity view to the entity view registry
You might have an entity view that you want to reuse in different collections.
##### FireCMS Cloud
In FireCMS Cloud, you can add it to the entity view registry in your
main `FireCMSAppConfig` export:
```tsx
const appConfig: FireCMSAppConfig = {
version: "1",
collections: async (props) => {
return ([
// ... your collections here
]);
},
entityViews: [{
key: "test-view",
name: "Test",
Builder: ({
collection,
entity,
modifiedValues
}) => Your view
}]
}
export default appConfig;
```
##### FireCMS PRO
In FireCMS PRO, you can add it to the entity view registry in your main
`FireCMS` component:
```tsx
//...
Your view
}]}
//...
/>
```
##### Using registered view
This will make the entity view available in the collection editor UI.
It is also possible to use the `entityView` prop in the collection
with the key of the entity view you want to use:
```tsx
const blogCollection = buildCollection({
id: "blog",
path: "blog",
name: "Blog",
entityViews: ["test-view"],
properties: {
// ... your blog properties here
}
});
```
#### Grouping views and subcollections
When an entity has multiple subcollections and custom views, the tab bar can become
crowded. You can use `viewGroups` to organize related tabs into dropdown menus.
Views listed in a group are removed from the top-level tabs and displayed under a
single dropdown button instead.
```tsx
const productsCollection = buildCollection({
id: "products",
path: "products",
name: "Products",
properties: {
// ... your product properties here
},
subcollections: [localesCollection, reviewsCollection],
entityViews: [
{
key: "preview",
name: "Product preview",
Builder: ProductPreview
}
],
viewGroups: [
{
name: "Related data",
views: ["locales", "reviews", "preview"]
}
]
});
```
In this example, the "locales" and "reviews" subcollection tabs and the "preview" custom view tab
will be grouped into a single "Related data" dropdown in the entity view tab bar.
The `views` array accepts:
- **Subcollection ids or paths** — the `id` (or `path` if no `id`) of a subcollection.
- **Custom view keys** — the `key` of an `EntityCustomView`.
## Permissions
You can define the `read`, `edit`, `create` and `delete` permissions at the collection
level, also depending on the logged-in user.
These define the actions that the logged user can perform over an entity.
#### Simple permissions
In the simpler case, you can directly assign the permissions
```tsx
buildCollection({
path: "products",
collection: productCollection,
name: "Products",
permissions: {
edit: true,
create: true,
delete: false
}
});
```
#### Advanced permissions
You can customise the permissions based on the user that is logged in, or any
other criteria that fits your use case.
You can use a `PermissionBuilder`, like in the example below, to customise the
actions based on the logged user.
In the example below we check if we have previously saved the role "admin"
in the extras field in the `AuthController`.
```tsx
buildCollection({
path: "products",
collection: productCollection,
name: "Products",
permissions: ({
entity,
path,
user,
authController,
context
}) => {
const isAdmin = authController.extra?.roles.includes("admin");
return ({
edit: isAdmin,
create: isAdmin,
delete: isAdmin
});
}
});
```
Note that you can set the `extra` parameter in the `AuthController` to any data
that makes sense to you. Suggested places where you may want to set that
parameter are `Authenticator` since it is initialised
before the rest of the app.
Quick example of how the `extra.roles` field in the previous example is
initialised:
```tsx
const myAuthenticator: Authenticator = async ({
user,
authController,
dataSource
}) => {
// This is an example of retrieving async data related to the user
// and storing it in the controller's extra field
const sampleUserData = await Promise.resolve({
roles: ["admin"]
});
authController.setExtra(sampleUserData);
console.log("Allowing access to", user);
return true; // Allow
};
```
## Exporting data
Every collection view is exportable by default and will include a button for
exporting data in `csv` format.
You can switch off the exporting function by setting the `exportable` parameter
in your collection to `false`
All the regular columns are exported, but not the additional fields that you
set up in your collection view, since you can build them with any React
component.
If you need to add additional fields in your export file, you can create
them by setting an `ExportConfig` in your `exportable` prop:
```tsx
export const sampleAdditionalExportColumn: ExportMappingFunction = {
key: "extra",
builder: async ({ entity }) => {
await new Promise(resolve => setTimeout(resolve, 100));
return "Additional exported value " + entity.id;
}
};
const blogCollection = buildCollection({
path: "blog",
collection: blogCollection,
name: "Blog",
exportable: {
additionalFields: [sampleAdditionalExportColumn]
},
});
```
## Additional columns/fields
If you would like to include a column that does not map directly to a property,
you can use the `additionalFields` field, providing a
`AdditionalFieldDelegate`, which includes an id, a title, and a builder that
receives the corresponding entity.
In the builder you can return any React Component.
:::note
If your additional field depends on the value of another property of the entity
you can define the `dependencies` prop as an array of property keys so that
the data is always updated.
This will trigger a rerender whenever there is a change in any of the specified
property values.
:::
##### Example
```tsx
type User = { name: string }
export const fullNameAdditionalField: AdditionalFieldDelegate = {
key: "full_name",
name: "Full Name",
Builder: ({ entity }) => {
let values = entity.values;
return typeof values.name === "string" ? values.name.toUpperCase() : "No name provided";
},
dependencies: ["name"]
};
const usersCollection = buildCollection({
path: "users",
name: "User",
properties: {
name: { dataType: "string", name: "Name" }
},
additionalFields: [
fullNameAdditionalField
]
});
```
##### Advanced example
```tsx
export const productAdditionalField: AdditionalFieldDelegate = {
key: "spanish_title",
name: "Spanish title",
Builder: ({ entity, context }) =>
entity.values.name)
}/>
};
```
:::tip
`AsyncPreviewComponent` is a utility component provided by FireCMS that
allows you to render the result of an async computation (such as fetching data
from a subcollection, like in this case). It will display a skeleton loading
indicator in the meantime.
:::
## Text search
:::note[The solution described here is specific for Firestore]
If you are developing your own datasource, you are free to implement text search in
whatever way it makes sense.
:::
Firestore does not support native text search, so we need to rely on external
solutions. If you specify a `textSearchEnabled` flag to the **collection**, you
will see a search bar on top of the collection view.
### Search Options
| Option | Cost | Setup | Best For |
|--------|------|-------|----------|
| **Typesense Extension** (Recommended) | ~$7-14/month flat | 5 min | Most projects |
| **Algolia** | Per-query pricing | 15 min | Enterprise, geo-search |
| **Local Text Search** | Free | 1 min | Small collections (<1000 docs) |
---
### Using Typesense (Recommended)
The **FireCMS Typesense Extension** deploys a Typesense search server on a Compute Engine VM and automatically syncs your Firestore data. Features:
- 🔍 **Typo-tolerant search** - "headphnes" matches "headphones"
- ⚡ **Sub-millisecond responses**
- 💰 **Flat monthly cost** - No per-query charges
- 🔄 **Real-time sync** - Documents auto-index on create/update/delete
#### Installation
**Prerequisites:**
- Firebase project with Firestore
- GCP billing enabled
- [gcloud CLI](https://cloud.google.com/sdk/docs/install) installed
**Step 1: Install the extension**
```bash
firebase ext:install https://github.com/firecmsco/typesense-extension --project=YOUR_PROJECT_ID
```
**Step 2: Grant permissions**
```bash
export PROJECT_ID=your-project-id
export EXT_INSTANCE_ID=typesense-search ## Default extension name
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:ext-${EXT_INSTANCE_ID}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/compute.admin" --condition=None
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:ext-${EXT_INSTANCE_ID}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/secretmanager.admin" --condition=None
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:ext-${EXT_INSTANCE_ID}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/datastore.user" --condition=None
```
**Step 3: Provision the search server**
```bash
curl "https://REGION-PROJECT_ID.cloudfunctions.net/ext-typesense-search-provisionSearchNode"
```
Replace `REGION` with your functions region (e.g., `us-central1`) and `PROJECT_ID` with your project.
Wait ~2 minutes. Existing documents are automatically indexed.
**Step 4: (Optional) Enable public search access**
```bash
gcloud functions add-iam-policy-binding ext-${EXT_INSTANCE_ID}-api \
--member="allUsers" \
--role="roles/cloudfunctions.invoker" \
--region=REGION \
--project=${PROJECT_ID}
```
#### Using Typesense in FireCMS Cloud
Navigate to **Project Settings** and configure:
| Setting | Value |
|---------|-------|
| **Region** | Your extension's region (e.g., `us-central1`) |
| **Extension Instance ID** | Default: `typesense-search` |
That's it! FireCMS Cloud automatically connects to your Typesense instance.
#### Using Typesense in Self-Hosted FireCMS
```typescript
const textSearchControllerBuilder = buildFireCMSSearchController({
region: "us-central1", // Your extension's region
extensionInstanceId: "typesense-search" // Default name
});
export function App() {
const firestoreDelegate = useFirestoreDelegate({
firebaseApp,
textSearchControllerBuilder
});
// ... rest of your app
}
```
#### Using Typesense Directly (Without FireCMS)
Search via the API proxy endpoint:
```typescript
const response = await fetch(
"https://REGION-PROJECT_ID.cloudfunctions.net/ext-typesense-search-api/collections/products/documents/search",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
q: "blue wireless headphones",
query_by: "name,description"
})
}
);
const results = await response.json();
```
See [Typesense API docs](https://typesense.org/docs/latest/api/) for all available endpoints.
---
### Using Algolia
Algolia is a managed search service with per-query pricing. Best for enterprise needs or advanced features like geo-search.
You need to define a `FirestoreTextSearchControllerBuilder` and add it to your config.
Set up an Algolia account and sync documents using their [Firebase extension](https://extensions.dev/extensions/algolia/firestore-algolia-search).
#### Using Algolia in FireCMS Cloud
We provide a utility method for performing searches in Algolia `performAlgoliaTextSearch`.
You need to import the `algoliasearch` library and create an Algolia client.
Then you can use the `performAlgoliaTextSearch` method to perform the search.
In your controller, you can define the paths you want to support and the search.
The paths not specified can still be searched with the local text search.
Example:
```tsx
const client: SearchClient | undefined = algoliasearch("YOUR_ALGOLIA_APP_ID", "YOUR_ALGOLIA_SEARCH_KEY");
const algoliaSearchController = buildExternalSearchController({
isPathSupported: (path) => path === "products",
search: async ({
path,
searchString
}) => {
if (path === "products") {
return performAlgoliaTextSearch(client, "products", searchString);
}
return undefined;
}
});
const appConfig: FireCMSAppConfig = {
version: "1",
textSearchControllerBuilder: algoliaSearchController,
// ...
}
```
#### Using Algolia in self-hosted FireCMS
For self-hosted FireCMS, you need to define a `FirestoreTextSearchControllerBuilder`.
```tsx
const client: SearchClient | undefined = algoliasearch("YOUR_ALGOLIA_APP_ID", "YOUR_ALGOLIA_SEARCH_KEY");
const algoliaSearchController = buildExternalSearchController({
isPathSupported: (path) => path === "products",
search: async ({
path,
searchString
}) => {
if (path === "products")
return performAlgoliaTextSearch(client, "products", searchString);
return undefined;
}
});
export function App() {
// ...
const firestoreDelegate = useFirestoreDelegate({
firebaseApp,
textSearchControllerBuilder: algoliaSearchControllerBuilder
});
// ...
}
```
#### Local text search
Since FireCMS v3 we provide a local text search implementation. This is useful
for small collections or when you want to provide a quick way to search through
your data.
However, for larger collections, you will want to use an **external search**
provider, such as Algolia. This is the recommended approach.
You can use local text search in FireCMS Cloud, or in self-hosted versions.
For FireCMS Cloud, you just need to enable it in the UI.
For self-hosted versions, you can enable it by setting the `localTextSearchEnabled` in `useFirestoreDelegate`.
Then you need to mark each collection with `textSearchEnabled: true`.
If you have declared an external indexing provider, the local text search will be
effective **only for the paths not supported by the external provider**.
#### Using an external search provider
When using an external search provider, you need to implement a `FirestoreTextSearchController`.
## Dynamic collections
FireCMS offers the possibility to define collections dynamically. This means
that collections can be built asynchronously, based on the logged-in user,
based on the data of other collections, or based on any other arbitrary
condition.
Instead of defining your collections as an array, use a `EntityCollectionsBuilder`,
a function that returns a promise of an object containing the collections.
```tsx
// ...
const collectionsBuilder: EntityCollectionsBuilder = async ({
user,
authController,
dataSource
}) =>
({
collections: [
buildCollection({
path: "products",
properties: {}, // ...
name: "Products"
})
]
});
```
:::note
If you want to make customizations at the property level only, check the
[conditional fields](https://firecms.co/docs/properties/conditional_fields) section. But note that conditional fields are not
suitable for asynchronous operations.
:::
#### Fetch data from a different collection
It may be the case that a collection config depends on the data of another
one. For example, you may want to fetch the enum values of a property from
a different collection.
In this example we will fetch data from a collection called `categories` and
use it to populate the enum values of a property called `category`, in the `products`
collection.
```tsx
const collectionsBuilder: EntityCollectionsBuilder = async ({
user,
authController,
dataSource
}) => {
// let's assume you have a database collection called "categories"
const categoriesData: Entity[] = await dataSource.fetchCollection({
path: "categories"
});
return {
collections: [
buildCollection({
id: "products",
path: "products",
properties: {
// ...
category: {
dataType: "string",
name: "Category",
// we can use the enumValues property to define the enum values
// the stored value will be the id of the category
// and the UI label will be the name of the category
enumValues: categoriesData.map((category: any) => ({
id: category.id,
label: category.values.name
}))
}
// ...
},
name: "Products"
})
]
}
};
```
#### Use in conjunction with authentication
The `AuthController` handles the auth state. It can also be used to store any
arbitrary object related to the user.
A typical use case is to store some additional data related to the user, for
example, the roles or the permissions.
```tsx
const myAuthenticator: Authenticator = useCallback(async ({
user,
authController
}) => {
if (user?.email?.includes("flanders")) {
throw Error("Stupid Flanders!");
}
console.log("Allowing access to", user?.email);
// This is an example of retrieving async data related to the user
// and storing it in the controller's extra field.
const sampleUserRoles = await Promise.resolve(["admin"]);
authController.setExtra(sampleUserRoles);
return true;
}, []);
```
Then you can access the extra data in the `collectionsBuilder` callback.
```tsx
const collectionsBuilder: EntityCollectionsBuilder = useCallback(async ({
user,
authController,
dataSource
}) => {
const userRoles = authController.extra;
if (userRoles?.includes("admin")) {
return {
collections: [
buildCollection({
path: "products",
properties: {}, // ...
name: "Products"
})
]
};
} else {
return {
collections: []
};
}
}, []);
```
#### Where to use the `collectionsBuilder`
In the **Cloud version** of FireCMS, simply add the `collectionsBuilder` to the `collections` prop of your main app
config.
```tsx
const collectionsBuilder: EntityCollectionsBuilder = async ({
user,
authController,
dataSource
}) => {
return {
collections: [] // your collections here
};
};
export const appConfig: FireCMSAppConfig = {
version: "1",
collections: collectionsBuilder
};
```
In the **PRO version** of FireCMS, you can use the `collectionsBuilder` in the `useBuildNavigationController` hook.
```tsx
const navigationController = useBuildNavigationController({
collections: collectionsBuilder
});
```
## Collection Bar Actions

You can add your custom components to the collection bar.
This is useful to add actions that are specific to the collection you are working with.
For example, you could add a button to export the **selected data**, or a button to trigger a specific **action in your backend**.
You can also retrieve the **selected filters** and modify them.
#### Sample retrieving selected entities
You need to define a component that receives `CollectionActionsProps` as props.
```tsx
export function SampleCollectionActions({ selectionController }: CollectionActionsProps) {
const snackbarController = useSnackbarController();
const onClick = (event: React.MouseEvent) => {
const selectedEntities = selectionController?.selectedEntities;
const count = selectedEntities ? selectedEntities.length : 0;
snackbarController.open({
type: "success",
message: `User defined code here! ${count} products selected`
});
};
return (
);
}
```
then just add it to your collection configuration:
```tsx
export const productCollection: EntitySchema = buildCollection({
name: "Products",
Actions: SampleCollectionActions,
// ...
});
```
#### Sample modifying filters
This is an example of how you can modify the filters in the collection bar:
```tsx
export function CustomFiltersActions({
tableController
}: CollectionActionsProps) {
const filterValues = tableController.filterValues;
const categoryFilter = filterValues?.category;
const categoryFilterValue = categoryFilter?.[1];
const updateFilter = (value: string | null) => {
const newFilter = {
...filterValues
};
if (value) {
newFilter.category = ["==", value];
} else {
delete newFilter.category;
}
tableController.setFilterValues?.(newFilter);
};
return (
);
}
```
then just add it to your collection configuration:
```tsx
export const productCollection: EntitySchema = buildCollection({
name: "Products",
Actions: CustomFiltersActions,
// ...
});
```
### CollectionActionsProps
The following properties are available on the `CollectionActionsProps` interface:
- **`path`**: Full collection path of this entity. This is the full path, like `users/1234/addresses`.
- **`relativePath`**: Path of the last collection, like `addresses`.
- **`parentCollectionIds`**: Array of the parent path segments like `['users']`.
- **`collection`**: The collection configuration.
- **`selectionController`**: Use this controller to get the selected entities and to update the selected entities state.
- **`tableController`**: Use this controller to get the table controller and to update the table controller state.
- **`context`**: Context of the app status.
- **`collectionEntitiesCount`**: Count of the entities in this collection.
## Collections Groups
You can now use Firestore collection groups in FireCMS. This allows you to
query across multiple collections with the same name. For example, you could
have a collection group called `products` that contains all the products
from different `stores`.
In our demo project, we have a collection group called `locales` that
contains all the locales for the different `products`.
See the demo project [here](https://demo.firecms.co/c/locales).
FireCMS will generate an additional column in the collection view to
with references to all the parent collections that are part of the
configuration.
In order to use collection groups, you need to specify the `collectionGroup`
property in the `Collection` configuration.
```tsx
export const localeCollectionGroup = buildCollection({
name: "Product locales group",
path: "locales",
description: "This is a collection group related to the locales subcollection of products",
collectionGroup: true,
properties: {
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
// ...
},
});
```
:::note
Depending on your Firestore rules, you may need to add another
rule to allow collection group queries. For example:
```text
match /{path=**}/locales/{document=**} {
allow read, write: if true;
}
```
When doing a collection group query, the path will be something like
`/products/{productId}/locales/{localeId}`. But the query will go to all
the collections called `locales` in your database. That is why you might need
to add a rule like the one above.
:::
## Entity actions
Entities can be edited, deleted and duplicated by default.
The default actions are enabled or disabled based on the permissions
of the user in the collection.
If you need to add custom actions, you can do so by defining them in the
`entityActions` prop of the collection.
You can also define entity actions globally, and they will be available in all collections.
This is useful for actions that are not specific to a single collection, like a "Share" action.
When defining a global entity action, you must provide a unique `key` property.
The actions will be shown in the menu of the collection view by default
and in the form view if `includeInForm` is set to true.
You can access all the controllers of FireCMS in the `context`. That is useful for accessing the data source,
modifying data, accessing storage, opening dialogs, etc.
In the `icon` prop, you can pass a React element to show an icon next to the action name.
We recommend using any of the [FireCMS icons](/docs/icons), which are available in the `@firecms/ui` package.
#### Defining actions at the collection level
```tsx
export const productsCollection = buildCollection({
id: "products",
path: "products",
name: "Products",
singularName: "Product",
icon: "shopping_cart",
description: "List of the products currently sold in our shop",
entityActions: [
{
icon: ,
name: "Archive",
onClick({
entity,
collection,
context,
}): Promise {
// note that you can access all the controllers in the context
const dataSource = context.dataSource;
// Add your code here
return Promise.resolve(undefined);
}
}
],
properties: {}
});
````
#### Defining actions globally
You can define entity actions globally by passing them to the `FireCMS` component if you are self-hosting,
or in the `FireCMSAppConfig` if you are using FireCMS Cloud.
```tsx
// Self-hosted
,
onClick: ({ entity, context }) => {
// Your share logic here
}
}]}
{...otherProps}
/>
```
```tsx
// FireCMS Cloud
const appConfig: FireCMSAppConfig = {
entityActions: [{
key: "share",
name: "Share",
icon: ,
onClick: ({ entity, context }) => {
// Your share logic here
}
}],
// ...other config
};
```
##### EntityAction
* `name`: Name of the action
* `key`?: Key of the action. You only need to provide this if you want to
override the default actions, or if you are defining the action globally.
The default actions are:
* `edit`
* `delete`
* `copy`
* `icon`?: React.ReactElement Icon of the action
* `onClick`: (props: EntityActionClickProps) =\> Promise
Function to be called when the action is clicked
* `collapsed`?: boolean Show this action collapsed in the menu of the collection view. Defaults to true. If false, the
action will be shown in the menu
* `includeInForm`?: boolean Show this action in the form, defaults to true
* `disabled`?: boolean Disable this action, defaults to false
##### EntityActionClickProps
* `entity`: Entity being edited
* `context`: FireCMSContext, used for accessing all the controllers
* `fullPath`?: string
* `fullIdPath`?: string
* `collection`?: EntityCollection
* `formContext`?: FormContext, present if the action is being called from a form.
* `selectionController`?: SelectionController, used for accessing the selected entities or modifying the selection
* `highlightEntity`?: (entity: Entity) => void
* `unhighlightEntity`?: (entity: Entity) => void
* `onCollectionChange`?: () => void
* `sideEntityController`?: SideEntityController
* `view`: "collection" | "form"
* `openEntityMode`: "side_panel" | "full_screen"
* `navigateBack`?: () => void
### Examples
Let's build an example where we add an action to archive a product.
When the action is clicked, we will call a Google Cloud Function that will run some business logic in the backend.
#### Using the `fetch` API
You can use the standard `fetch` API to call any HTTP endpoint, including a Google Cloud Function. This is a general-purpose method that works with any backend.
```tsx
export const productsCollection = buildCollection({
id: "products",
path: "products",
// other properties
entityActions: [
{
icon: ,
name: "Archive",
collapsed: false,
onClick({
entity,
context,
}) {
const snackbarController = context.snackbarController;
return fetch("[YOUR_ENDPOINT]/archiveProduct", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
productId: entity.id
})
}).then(() => {
snackbarController.open({
message: "Product archived",
type: "success"
});
}).catch((error) => {
snackbarController.open({
message: "Error archiving product",
type: "error"
});
});
}
}
],
});
```
#### Using the Firebase Functions SDK
If you're using Firebase, the recommended approach is to use the Firebase Functions SDK. It simplifies calling functions and automatically handles authentication tokens.
First, ensure you have the `firebase` package installed and initialized in your project.
Then, you can define your action like this:
```tsx
// Initialize Firebase Functions
// Make sure you have initialized Firebase elsewhere in your app
const functions = getFunctions();
const archiveProductCallable = httpsCallable(functions, 'archiveProduct');
export const productsCollection = buildCollection({
id: "products",
path: "products",
// other properties
entityActions: [
{
icon: ,
name: "Archive with Firebase",
collapsed: false,
async onClick({
entity,
context,
}) {
const snackbarController = context.snackbarController;
try {
await archiveProductCallable({ productId: entity.id });
snackbarController.open({
message: "Product archived successfully",
type: "success"
});
} catch (error) {
console.error("Error archiving product:", error);
snackbarController.open({
message: "Error archiving product: " + error.message,
type: "error"
});
}
}
}
],
});
```
## Properties
Properties define each **field** in a form or column in a collection included in an
entity collection.
You can build properties by creating the object directly or by
using the helper method `buildProperty` (just the identity function that uses
the Typescript type system to validate the input).
You may also want to update properties **dynamically**, based on the entityId, the
path or the current values. Check
the [conditional fields section](conditional_fields)
Check the different fields available in the fields section, or the
different property configurations.
## Text fields
#### Simple text field

The most basic widget is the text field, which allows the user to input simple
strings.
If you define a string property with no other configuration parameters, you will
get a text field:
```typescript jsx
buildProperty({
dataType: "string",
name: "Name",
validation: {
// ...
}
});
```
The data type is [`string`](https://firecms.co/docs/config/string) or [`number`](https://firecms.co/docs/config/number).
Internally the component used
is [`TextFieldBinding`](https://firecms.co/docs/../api/functions/TextFieldBinding).
#### Multi line text field

Use a multiline field when you want to enable the user to input strings that may
contain line breaks.
Set the `multiline` flag to `true` in a string property.
```typescript jsx
buildProperty({
dataType: "string",
name: "Description",
multiline: true,
validation: {
// ...
}
});
```
The data type is [`string`](https://firecms.co/docs/config/string).
Internally the component used
is [`TextFieldBinding`](https://firecms.co/docs/../api/functions/TextFieldBinding).
#### Markdown text field

You can use a markdown field when you would like the end user to use advanced
editing capabilities of text using the Markdown format.
Set the `markdown` flag to `true` in a string property.
```typescript jsx
buildProperty({
dataType: "string",
name: "Blog text",
markdown: true,
validation: {
// ...
}
});
```
The data type is [`string`](https://firecms.co/docs/config/string).
Internally the component used
is [`MarkdownEditorFieldBinding`](https://firecms.co/docs/../api/functions/MarkdownEditorFieldBinding).
#### Url text field

You can use a URL field when you would like to ensure that the input of the end
user is a valid URL.
Set the `url` flag to `true` in a string property.
```typescript jsx
buildProperty({
dataType: "string",
name: "Amazon link",
url: true,
validation: {
// ...
}
});
```
The data type is [`string`](https://firecms.co/docs/config/string).
Internally the component used
is [`TextFieldBinding`](https://firecms.co/docs/../api/functions/TextFieldBinding).
#### Email field

You can use an email field when you would like to ensure that the input of the
end user is a valid email.
Set the `email` flag to `true` in a string property.
```typescript jsx
buildProperty({
dataType: "string",
name: "User email",
email: true,
validation: {
// ...
}
});
```
The data type is [`string`](https://firecms.co/docs/config/string).
Internally the component used
is [`TextFieldBinding`](https://firecms.co/docs/../api/functions/TextFieldBinding).
## Select fields
#### Simple select field

You can use a simple select field when you would like allow the selection of a
single value among a limited set of options. Each entry will have a key and a
label. You can also customise the color of each entry or disable certain options.
Set the `enumValues` prop to a valid configuration in a string property. You can
define those values as an array
of [`EnumValueConfig`](https://firecms.co/docs/../api/type-aliases/EnumValueConfig)
or simply as an object with key/value pairs:
```typescript jsx
buildProperty({
dataType: "string",
name: "Category",
enumValues: {
art_design_books: "Art and design books",
backpacks: "Backpacks and bags",
bath: "Bath",
bicycle: "Bicycle",
books: "Books"
}
});
```
or
```typescript jsx
buildProperty({
dataType: "string",
name: "Currency",
enumValues: [
{ id: "EUR", label: "Euros", color: "blueDark" },
{ id: "DOL", label: "Dollars", color: "greenLight" }
]
});
```
The data type is [`string`](https://firecms.co/docs/config/string) or [`number`](https://firecms.co/docs/config/number).
Internally the component used
is [`SelectFieldBinding`](https://firecms.co/docs/../api/functions/SelectFieldBinding).
#### Multiple select field

You can use a multiple select field when you would like allow the selection of a
zero or more values among a limited set of options. Each entry will have a key
and a label. You can also customise the color of each entry or disable certain options.
Set the `enumValues` prop to a valid configuration in a string property. You can
define those values as an array
of [`EnumValueConfig`](https://firecms.co/docs/../api/type-aliases/EnumValueConfig)
or simply as an object with key/value pairs:
```typescript jsx
buildProperty({
name: "Available locales",
dataType: "array",
of: {
dataType: "string",
enumValues: {
"es": "Spanish",
"en": "English",
"fr": {
id: "fr",
label: "French",
disabled: true
}
}
},
defaultValue: ["es"]
});
```
The data type is [`array`](https://firecms.co/docs/config/array) with either string or number
properties as the `of` prop, using enum values.
Internally the component used
is [`SelectFieldBinding`](https://firecms.co/docs/../api/functions/SelectFieldBinding).
#### Customising the colors
You can pick the colors among a list of predefined values:
```tsx
import { ChipColorKey, ChipColorScheme } from "../components";
import { hashString } from "./hash";
export const CHIP_COLORS: Record = {
blueLighter: { color: "#cfdfff", text: "#102046" },
cyanLighter: { color: "#d0f0fd", text: "#04283f" },
tealLighter: { color: "#c2f5e9", text: "#012524" },
greenLighter: { color: "#d1f7c4", text: "#0b1d05" },
yellowLighter: { color: "#ffeab6", text: "#3b2501" },
orangeLighter: { color: "#fee2d5", text: "#6b2613" },
redLighter: { color: "#ffdce5", text: "#4c0c1c" },
pinkLighter: { color: "#ffdaf6", text: "#400832" },
purpleLighter: { color: "#ede2fe", text: "#280b42" },
grayLighter: { color: "#eee", text: "#040404" },
blueLight: { color: "#9cc7ff", text: "#102046" },
cyanLight: { color: "#77d1f3", text: "#04283f" },
tealLight: { color: "#72ddc3", text: "#012524" },
greenLight: { color: "#93e088", text: "#0b1d05" },
yellowLight: { color: "#ffd66e", text: "#3b2501" },
orangeLight: { color: "#ffa981", text: "#6b2613" },
redLight: { color: "#ff9eb7", text: "#4c0c1c" },
pinkLight: { color: "#f99de2", text: "#400832" },
purpleLight: { color: "#cdb0ff", text: "#280b42" },
grayLight: { color: "#ccc", text: "#040404" },
blueDark: { color: "#2d7ff9", text: "#fff" },
cyanDark: { color: "#18bfff", text: "#fff" },
tealDark: { color: "#20d9d2", text: "#fff" },
greenDark: { color: "#20c933", text: "#fff" },
yellowDark: { color: "#fcb400", text: "#fff" },
orangeDark: { color: "#ff6f2c", text: "#fff" },
redDark: { color: "#f82b60", text: "#fff" },
pinkDark: { color: "#ff08c2", text: "#fff" },
purpleDark: { color: "#8b46ff", text: "#fff" },
grayDark: { color: "#666", text: "#fff" },
blueDarker: { color: "#2750ae", text: "#cfdfff" },
cyanDarker: { color: "#0b76b7", text: "#d0f0fd" },
tealDarker: { color: "#06a09b", text: "#daf3e9" },
greenDarker: { color: "#338a17", text: "#d1f7c4" },
yellowDarker: { color: "#b87503", text: "#ffeab6" },
orangeDarker: { color: "#d74d26", text: "#fee2d5" },
redDarker: { color: "#ba1e45", text: "#ffdce5" },
pinkDarker: { color: "#b2158b", text: "#ffdaf6" },
purpleDarker: { color: "#6b1cb0", text: "#ede2fe" },
grayDarker: { color: "#444", text: "#eee" }
};
export function getColorSchemeForKey(key: ChipColorKey): ChipColorScheme {
return CHIP_COLORS[key];
}
export function getColorSchemeForSeed(seed: string): ChipColorScheme {
const hash: number = hashString(seed);
const colorKeys = Object.keys(CHIP_COLORS);
const index = hash % colorKeys.length;
return CHIP_COLORS[colorKeys[index]];
}
```
And you can also define custom colors using the HTML syntax `#AAAAAA`:
```typescript jsx
buildProperty({
dataType: "string",
name: "Currency",
enumValues: [
{ id: "EUR", label: "Euros", color: "blueDark" },
{
id: "DOL",
label: "Dollars",
color: {
color: "#FFFFFF",
text: "#333333",
}
}
]
});
```
## File upload
Use the file upload fields to allow users to upload images, documents or any
files to your storage solution (Firebase storage by default). This field is in
charge of uploading the file and saving the storage path as the value
of your property.
:::note
You can save the URL of the uploaded file, instead of the Storage pah,
by setting the `storeUrl`.
:::
You can also allow the upload of only some file types based on
the [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types)
, or restrict the file size.
If the file uploaded is an image, you can also choose to resize it before
it gets uploaded to the storage backend, with the `imageCompression` prop.
The complete list of params you can use when uploading files:
* `mediaType` Media type of this reference, used for displaying the
preview.
* `storagePath` Absolute path in your bucket. You can specify it
directly or use a callback
* `acceptedFiles`
File [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types)
that can be uploaded to this
reference. Note that you can also use the asterisk notation, so `image/*`
accepts any image file, and so on.
* `metadata` Specific metadata set in your uploaded file.
* `fileName` You can specify a fileName callback if you need to
customize the name of the file
* `storagePath` You can specify a storage path callback if you need to
customize the path where it is stored.
* `storeUrl` When set to `true`, this flag indicates that the download
URL of the file will be saved in Firestore instead of the Cloud
storage path. Note that the generated URL may use a token that, if
disabled, may make the URL unusable and lose the original reference to
Cloud Storage, so it is not encouraged to use this flag. Defaults to
false.
* `imageCompression` Use client side image compression and resizing
Will only be applied to these MIME types: `image/jpeg`, `image/png`
and `image/webp`
:::note
You can use some placeholders in the `storagePath` and `fileName` to
customize the path and name of the file. The available placeholders are:
- \{file\} - Full file name
- \{file.name\} - Name of the file without extension
- \{file.ext\} - Extension of the file
- \{rand\} - Random value used to avoid name collisions
- \{entityId\} - ID of the entity
- \{propertyKey\} - ID of this property
- \{path\} - Path of this entity
:::
#### Single file upload

```typescript jsx
buildProperty({
dataType: "string",
name: "Image",
storage: {
storagePath: "images",
acceptedFiles: ["image/*"],
maxSize: 1024 * 1024,
metadata: {
cacheControl: "max-age=1000000"
},
fileName: (context) => {
return context.file.name;
}
}
});
```
The data type is [`string`](https://firecms.co/docs/config/string).
Internally the component used
is [`StorageUploadFieldBinding`](https://firecms.co/docs/../api/functions/StorageUploadFieldBinding).
#### Multiple file upload

```typescript jsx
buildProperty({
dataType: "array",
name: "Images",
of: {
dataType: "string",
storage: {
storagePath: "images",
acceptedFiles: ["image/*"],
metadata: {
cacheControl: "max-age=1000000"
}
}
},
description: "This fields allows uploading multiple images at once"
});
```
The data type is [`array`](https://firecms.co/docs/config/array).
Internally the component used
is [`StorageUploadFieldBinding`](https://firecms.co/docs/../api/functions/StorageUploadFieldBinding).
#### Custom support for images, videos and audio
You are free to use the `storage` property to upload any kind of file, but
FireCMS also provides some custom support for images, videos and audio.
You don't need to make any specific changes and this behaviour is enabled by
default. FireCMS will automatically detect if the file is an image, video or
audio and will display the preview accordingly.
The MIME types supported for custom previews are:
- `image/*`
- `video/*`
- `audio/*`
(this includes all file formats related to these categories)
## Switch

Simple toggle for selecting `true` or `false` values.
```typescript jsx
buildProperty({
name: "Selectable",
dataType: "boolean"
});
```
The data type is [`boolean`](https://firecms.co/docs/config/boolean).
Internally the component used
is [`SwitchFieldBinding`](https://firecms.co/docs/../api/functions/SwitchFieldBinding).
## Date/time fields
Use the date/time fields to allow users to set dates, saved as Firestore timestamps.
You can choose between using dates or date/time fields.
Also you can create read-only fields that get updated automatically when
entities are created or updated
The data type is [`date`](https://firecms.co/docs/config/date).
Internally the component used
is [`DateTimeFieldBinding`](https://firecms.co/docs/../api/functions/DateTimeFieldBinding).
##### Date field

```typescript jsx
buildProperty({
dataType: "date",
name: "Expiry date",
mode: "date"
});
```
##### Date/time field

```typescript jsx
buildProperty({
dataType: "date",
name: "Arrival time",
mode: "date_time"
});
```
##### Update on creation
```typescript jsx
buildProperty({
dataType: "date",
name: "Created at",
autoValue: "on_create"
});
```
##### Update on update
```typescript jsx
buildProperty({
dataType: "date",
name: "Updated at",
autoValue: "on_update"
});
```
## References
Use reference fields when you need to establish relations between collections.
For example, you may have a product that is related to one category, or one
that has multiple purchases.
When you set up a FireCMS app, you define collections under paths (or path
aliases), and those are the paths that you use to configure reference
properties.
#### Single reference field

```typescript jsx
buildProperty({
dataType: "reference",
path: "users",
name: "Related client",
});
```
The data type is [`reference`](https://firecms.co/docs/config/reference)
Internally the component used
is [`ReferenceFieldBinding`](https://firecms.co/docs/../api/functions/ReferenceFieldBinding).
#### Multiple reference field

```typescript jsx
buildProperty({
dataType: "array",
name: "Related products",
of: {
dataType: "reference",
path: "products"
}
});
```
The data type is [`array`](https://firecms.co/docs/config/array) with a reference
property as the `of` prop.
Internally the component used
is [`ArrayOfReferencesFieldBinding`](https://firecms.co/docs/../api/functions/ArrayOfReferencesFieldBinding).
## Group

Use this field to group other groups into a single one, represented by an
expandable panel. This is useful for bundling together data into logical fields,
both from the UX and the data model perspective.
Group fields can be initially expanded or collapsed by default.
```typescript jsx
buildProperty({
name: "Address",
dataType: "map",
properties: {
street: {
name: "Street",
dataType: "string"
},
postal_code: {
name: "Postal code",
dataType: "number"
}
},
expanded: true
});
```
The data type is [`map`](https://firecms.co/docs/config/map).
Internally the component used
is [`MapFieldBinding`](https://firecms.co/docs/../api/functions/MapFieldBinding).
## Key/Value

Key/Value is a special field that allows you to input arbitrary key/value pairs.
You are able to use string as keys and any primitive type as value (including maps
and arrays).
To enable this widget, simply set the `dataType` to `map`, and the `keyValue` property
to `true`.
```typescript jsx
buildProperty({
dataType: "map",
name: "Key value",
keyValue: true
});
```
The data type is [`map`](https://firecms.co/docs/config/map).
Internally the component used
is [`KeyValueFieldBinding`](https://firecms.co/docs/../api/functions/KeyValueFieldBinding).
## Repeat

You can use a repeat field when you want to save multiple values in a property.
For example, you may want to save multiple pieces of text, like tags.
Please note that if you use an `array` property which uses an `of` prop, the
resulting field may be one of the specialized ones (such as select, file
upload or reference field). The repeat field will be used in the rest of cases.
This fields allows reordering of its entries.
This component can be expanded or collapsed by default.
```typescript jsx
buildProperty({
dataType: "array",
name: "Tags",
of: {
dataType: "string",
previewAsTag: true
},
expanded: true,
sortable: true, // default is true
canAddElements: true, // default is true
});
```
The data type is [`array`](https://firecms.co/docs/config/array).
Internally the component used
is [`RepeatFieldBinding`](https://firecms.co/docs/../api/functions/RepeatFieldBinding).
## Block

Block is a special field that allows you to build repeat fields where the
entries are dynamic. Each entry has a `type` selector that allows the end user
to chose among different properties.
It is useful when you want to give the flexibility of building complex
repeat structures to end users, such as blog entries.
This fields allows reordering of its entries.
This component can be expanded or collapsed by default.
```typescript jsx
buildProperty({
name: "Content",
dataType: "array",
oneOf: {
typeField: "type",
valueField: "value",
properties: {
images: {
dataType: "string",
name: "Image",
storage: {
storagePath: "images",
acceptedFiles: ["image/*"]
}
},
text: {
dataType: "string",
name: "Text",
markdown: true
},
products: {
name: "Products",
dataType: "array",
of: {
dataType: "reference",
path: "products",
previewProperties: ["name", "main_image"]
}
}
}
}
});
```
The data type is [`array`](https://firecms.co/docs/config/array).
Internally the component used
is [`BlockFieldBinding`](https://firecms.co/docs/../api/functions/BlockFieldBinding).
## Common config
Each property in the CMS has its own API, but they all share some **common props**:
* `dataType` Datatype of the property. (e.g. `string`, `number`, etc.)
* `name` Property name (e.g. Price).
* `description` Property description.
* `longDescription` Longer description of a field, displayed under a popover.
* `columnWidth` Width in pixels of this column in the collection view. If not
set, the width is inferred based on the other configurations.
* `readOnly`Is this a read only property. When set to true, it gets rendered as a
preview.
* `disabled`Is this field disabled. When set to true, it gets rendered as a
disabled field. You can also specify a configuration for defining the
behaviour of disabled properties (including custom messages, clear value on
disabled or hide the field completely)
[PropertyDisabledConfig](https://firecms.co/docs/../api/interfaces/PropertyDisabledConfig)
* `Field`
If you need to render a custom field, you can create a component that
takes `FieldProps` as props. You receive the value, a function to update
the value and additional utility props such as if there is an error. You
can customize it by passing custom props that are received in the
component. More details about how to
implement [custom fields](https://firecms.co/docs/custom_fields.mdx)
* `Preview`
Configure how a property is displayed as a preview, e.g. in the collection
view. You can customize it by passing custom props that are received in
the component. More details about how to
implement [custom previews](https://firecms.co/docs/custom_previews)
* `customProps`
Additional props that are passed to the components defined in `Field` or
in `Preview`.
* `defaultValue`
This value will be set by default for new entities.
## String
The **string property** is the most versatile field type in FireCMS. Use it for everything from simple text inputs to file uploads, rich text editors, and dropdowns. When building an **admin panel** for your **Firebase** app, string properties let you create:
- **Text fields**: Names, titles, descriptions
- **Select dropdowns**: Status fields, categories, options
- **File uploads**: Images, documents (stored in **Firebase Storage**)
- **Markdown editors**: Rich content with formatting
- **Email/URL fields**: Validated input types
```tsx
const nameProperty = buildProperty({
name: "Name",
description: "Basic string property with validation",
validation: { required: true },
dataType: "string"
});
```
#### `storage`
You can specify a `StorageMeta` configuration. It is used to
indicate that this string refers to a path in Google Cloud Storage.
* `mediaType` Media type of this reference, used for displaying the
preview.
* `acceptedFiles` File [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) that can be uploaded to this
reference. Note that you can also use the asterisk notation, so `image/*`
accepts any image file, and so on.
* `metadata` Specific metadata set in your uploaded file.
* `fileName` You can use this prop to customize the uploaded filename.
You can use a function as a callback or a string where you
specify some placeholders that get replaced with the corresponding values.
- `{file}` - Full file name
- `{file.name}` - Name of the file without extension
- `{file.ext}` - Extension of the file
- `{rand}` - Random value used to avoid name collisions
- `{entityId}` - ID of the entity
- `{propertyKey}` - ID of this property
- `{path}` - Path of this entity
* `storagePath` Absolute path in your bucket.
You can use a function as a callback or a string where you
specify some placeholders that get replaced with the corresponding values.
- `{file}` - Full file name
- `{file.name}` - Name of the file without extension
- `{file.ext}` - Extension of the file
- `{rand}` - Random value used to avoid name collisions
- `{entityId}` - ID of the entity
- `{propertyKey}` - ID of this property
- `{path}` - Path of this entity
* `includeBucketUrl` When set to `true`, FireCMS will store a fully-qualified
storage URL instead of just the storage path.
For Firebase Storage this is a `gs://...` URL, e.g.
`gs://my-bucket/path/to/file.png`.
Defaults to `false`.
* `storeUrl` When set to `true`, this flag indicates that the download
URL of the file will be saved in Firestore instead of the Cloud
storage path. Note that the generated URL may use a token that, if
disabled, may make the URL unusable and lose the original reference to
Cloud Storage, so it is not encouraged to use this flag. Defaults to
`false`.
* `maxSize` Max file size in bytes.
* `processFile` Use this callback to process the file before uploading it.
If you return `undefined`, the original file is uploaded.
* `postProcess` Postprocess the saved value (storage path, storage URL or download URL)
after it has been resolved.
* `previewUrl` Provide a custom preview URL for a given file name.
##### Images: resize/compress before upload
FireCMS supports client-side image optimization before upload:
* `imageResize` (recommended) Advanced image resizing and cropping configuration.
Only applied to images (`image/jpeg`, `image/png`, `image/webp`).
- `maxWidth`, `maxHeight`
- `mode`: `contain` or `cover`
- `format`: `original`, `jpeg`, `png`, `webp`
- `quality`: 0-100
* `imageCompression` (deprecated) Legacy image resizing/compression.
```tsx
const imageProperty = buildProperty({
dataType: "string",
storage: {
mediaType: "image",
storagePath: (context) => {
return "images";
},
acceptedFiles: ["image/*"],
fileName: (context) => {
return context.file.name;
},
includeBucketUrl: true,
imageResize: {
maxWidth: 1200,
maxHeight: 1200,
mode: "cover",
format: "webp",
quality: 85
}
}
});
```
#### `url`
If the value of this property is a URL, you can set this flag
to `true` to add a link, or one of the supported media types to render a
preview.
```tsx
const amazonLinkProperty = buildProperty({
dataType: "string",
name: "Amazon link",
url: true
});
```
You can also define the preview type for the url: `image`, `video` or `audio`:
```tsx
const imageProperty = buildProperty({
name: "Image",
dataType: "string",
url: "image",
});
```
#### `email`
If set to `true`, this field will be validated as an email address and
rendered with an email-specific input. This is useful for contact forms,
user profiles, or any field that should contain a valid email.
```tsx
const emailProperty = buildProperty({
name: "Email",
dataType: "string",
email: true
});
```
#### `userSelect`
This property is used to indicate that the string is a **user ID**, and
it will be rendered as a user picker. Note that the user ID needs to be the
one used in your authentication provider, e.g. Firebase Auth.
You can also use a property builder to specify the user path dynamically
based on other values of the entity.
```tsx
const assignedUserProperty = buildProperty({
name: "Assigned User",
dataType: "string",
userSelect: true
});
```
#### `enumValues`
You can use the enum values providing a map of possible exclusive values the
property can take, mapped to the label that it is displayed in the dropdown. You
can use a simple object with the format
`value` => `label`, or with the format `value`
=> [`EnumValueConfig`](https://firecms.co/docs/../api/type-aliases/EnumValueConfig) if you need extra
customization, (like disabling specific options or assigning colors). If you
need to ensure the order of the elements, you can pass a `Map` instead of a
plain object.
```tsx
const amazonLinkProperty = buildProperty({
dataType: "string",
name: "Amazon link",
enumValues: {
"es": "Spanish",
"de": "German",
"en": "English",
"it": "Italian",
"fr": {
id: "fr",
label: "French",
disabled: true
}
}
});
```
#### `multiline`
Is this string property long enough, so it should be displayed
in a multiple line field. Defaults to false. If set to `true`, the number
of lines adapts to the content.
```tsx
const property = buildProperty({
name: "Description",
dataType: "string",
multiline: true
});
```
#### `clearable`
Add an icon to clear the value and set it to `null`. Defaults to `false`
#### `markdown`
Should this string property be displayed as a markdown field.
If `true`, the field is rendered as a text editors that supports markdown
highlight syntax. It also includes a preview of the result.
```tsx
const property = buildProperty({
dataType: "string",
name: "Text",
markdown: true
});
```
#### `previewAsTag`
Should this string be rendered as a tag instead of just text.
```tsx
const property = buildProperty({
name: "Tags",
description: "Example of generic array",
dataType: "array",
of: {
dataType: "string",
previewAsTag: true
}
});
```
#### `validation`
* `required` Should this field be compulsory.
* `requiredMessage` Message to be displayed as a validation error.
* `unique` The value of this field must be unique in this collection.
* `uniqueInArray` If you set it to `true`, the user will only be allowed to
have the value of that property once in the parent
`ArrayProperty`. It works on direct children properties or on first level
children of a `MapProperty` (if set as the `.of` property of
the `ArrayProperty`).
* `length` Set a required length for the string value.
* `min` Set a minimum length limit for the string value.
* `max` Set a maximum length limit for the string value.
* `matches` Provide an arbitrary regex to match the value against.
* `email` Validates the value as an email address via a regex.
* `url` Validates the value as a valid URL via a regex.
* `trim` Transforms string values by removing leading and trailing
whitespace.
* `lowercase` Transforms the string value to lowercase.
* `uppercase` Transforms the string value to uppercase.
---
Based on your configuration the form field widgets that are created are:
- [`TextFieldBinding`](https://firecms.co/docs/../api/functions/TextFieldBinding) generic text field
- [`SelectFieldBinding`](https://firecms.co/docs/../api/functions/SelectFieldBinding) if `enumValues`
are set in the string config, this field renders a select
where each option is a colored chip.
- [`StorageUploadFieldBinding`](https://firecms.co/docs/../api/functions/StorageUploadFieldBinding)
the property has a
storage configuration.
- [`MarkdownEditorFieldBinding.`](https://firecms.co/docs/../api/functions/MarkdownEditorFieldBinding) the
property has a
markdown configuration.
Links:
- [API](https://firecms.co/docs/../api/interfaces/StringProperty)
## Number
```tsx
const rangeProperty = buildProperty({
name: "Range",
validation: {
min: 0,
max: 3
},
dataType: "number"
});
```
#### `clearable`
Add an icon to clear the value and set it to `null`. Defaults to `false`
#### `enumValues`
You can use the enum values providing a map of possible
exclusive values the property can take, mapped to the label that it is
displayed in the dropdown.
```tsx
const property = buildProperty({
name: "Status",
dataType: "number",
enumValues: [
buildEnumValueConfig({
id: "-1",
label: "Lightly tense",
color: "redLighter"
}),
buildEnumValueConfig({
id: "0",
label: "Normal",
color: "grayLight"
}),
buildEnumValueConfig({
id: "1",
label: "Lightly relaxed",
color: "blueLighter"
})
]
});
```
#### `validation`
* `required` Should this field be compulsory.
* `requiredMessage` Message to be displayed as a validation error.
* `min` Set the minimum value allowed.
* `max` Set the maximum value allowed.
* `lessThan` Value must be less than.
* `moreThan` Value must be more than.
* `positive` Value must be a positive number.
* `negative` Value must be a negative number.
* `integer` Value must be an integer.
---
The widgets that get created are
- [`TextFieldBinding`](https://firecms.co/docs/../api/functions/TextFieldBinding) generic text field
- [`SelectFieldBinding`](https://firecms.co/docs/../api/functions/SelectFieldBinding) if `enumValues` are set in the string config, this field renders a select
where each option is a colored chip.
Links:
- [API](https://firecms.co/docs/../api/interfaces/NumberProperty)
## Boolean
```tsx
const availableProperty = buildProperty({
name: "Available",
dataType: "boolean"
});
```
#### `validation`
* `required` Should this field be compulsory.
* `requiredMessage` Message to be displayed as a validation error.
---
The widget that gets created is
- [`SwitchFieldBinding`](https://firecms.co/docs/../api/functions/SwitchFieldBinding) simple boolean switch
Links:
- [API](https://firecms.co/docs/../api/interfaces/BooleanProperty)
## Reference
```tsx
const productsReferenceProperty = buildProperty({
name: "Product",
dataType: "reference",
path: "products",
previewProperties: ["name", "main_image"]
});
```
#### `path`
Absolute collection path of the collection this reference
points to. The schema of the entity is inferred based on the root navigation,
so the filters and search delegate existing there are applied to this view as
well.
#### `previewProperties`
List of properties rendered as this reference preview.
Defaults to first 3.
#### `forceFilter`
Force a filter in the reference selection. If applied, the rest of the filters
will be disabled. Filters applied with this prop cannot be changed.
e.g. `forceFilter: { age: [">=", 18] }`
#### `validation`
* `required` Should this field be compulsory.
* `requiredMessage` Message to be displayed as a validation error.
#### `includeId`
Should the reference include the ID of the entity. Defaults to `true`.
#### `includeEntityLink`
Should the reference include a link to the entity (open the entity details). Defaults to `true`.
#### `defaultValue`
Default value for this property.
You can set the default value by defining an EntityReference:
```tsx
const productsReferenceProperty = buildProperty({
name: "Product",
dataType: "reference",
path: "products",
defaultValue: new EntityReference("B000P0MDMS", "products")
});
```
---
The widget that gets created is
- [`ReferenceFieldBinding`](https://firecms.co/docs/../api/functions/ReferenceFieldBinding) Field
that opens a
reference selection dialog
Links:
- [API](https://firecms.co/docs/../api/interfaces/ReferenceProperty)
## Date/Time
```tsx
const publicationProperty = buildProperty({
name: "Publication date",
dataType: "date"
});
```
#### `autoValue` "on_create" | "on_update"
Use this prop to update this date automatically upon entity creation
or update.
#### `mode` "date" | "date_time"
Set the granularity of the field to a date, or date + time.
Defaults to `date_time`.
#### `clearable`
Add an icon to clear the value and set it to `null`. Defaults to `false`
#### `validation`
* `required` Should this field be compulsory.
* `requiredMessage` Message to be displayed as a validation error.
* `min` Set the minimum date allowed.
* `max` Set the maximum date allowed.
---
The widget that gets created is
- [`DateTimeFieldBinding`](https://firecms.co/docs/../api/functions/DateTimeFieldBinding) Field that allows selecting a date
Links:
- [API](https://firecms.co/docs/../api/interfaces/DateProperty)
## Array
#### `of`
The property of this array.
You can specify any property (except another Array property, since
Firestore does not support it)
You can leave this field empty only if you are providing a custom field or
provide a `oneOf` field otherwise an error will be thrown.
Example `of` array property:
```tsx
const productReferences = buildProperty({
name: "Products",
dataType: "array",
of: {
dataType: "reference",
path: "products",
previewProperties: ["name", "main_image"]
}
});
```
##### tuple
You can also specify an array of properties to define a tuple:
```tsx
const tupleDates = buildProperty({
name: "Date Range (Start to End)",
dataType: "array",
of: [
{
name: "Start Date",
dataType: "date"
},
{
name: "End Date",
dataType: "date"
}
]
});
```
#### `oneOf`
Use this field if you would like to have an array of properties.
It is useful if you need to have values of different types in the same
array.
Each entry of the array is an object with the shape:
```
{ type: "YOUR_TYPE", value: "YOUR_VALUE"}
```
Note that you can use any property so `value` can take any value (strings,
numbers, array, objects...)
You can customise the `type` and `value` fields to suit your needs.
An example use case for this feature may be a blog entry, where you have
images and text blocks using markdown.
Example of `oneOf` field:
```tsx
const contentProperty = buildProperty({
name: "Content",
description: "Example of a complex array with multiple properties as children",
validation: { required: true },
dataType: "array",
oneOf: {
typeField: "type",
valueField: "value",
properties: {
name: {
name: "Title",
dataType: "string"
},
text: {
dataType: "string",
name: "Text",
markdown: true
}
}
}
});
```
#### `sortable`
Controls whether elements in this array can be reordered. Defaults to `true`.
This property has no effect if `disabled` is set to `true`.
Example:
```tsx
const tagsProperty = buildProperty({
name: "Tags",
dataType: "array",
of: {
dataType: "string",
previewAsTag: true
},
sortable: false // disable reordering
});
```
#### `canAddElements`
Controls whether elements can be added to the array. Defaults to `true`.
This property has no effect if `disabled` is set to `true`.
Example:
```tsx
const readOnlyTagsProperty = buildProperty({
name: "Tags",
dataType: "array",
of: {
dataType: "string"
},
canAddElements: false // prevent adding new tags
});
```
#### `expanded`
Determines whether the field should be initially expanded. Defaults to `true`.
#### `minimalistView`
When set to `true`, displays the child properties directly without being wrapped in an extendable panel.
#### `validation`
* `required` Should this field be compulsory.
* `requiredMessage` Message to be displayed as a validation error.
* `min` Set the minimum length allowed.
* `max` Set the maximum length allowed.
---
Based on your configuration the form field widgets that are created are:
- [`RepeatFieldBinding`](https://firecms.co/docs/../api/functions/RepeatFieldBinding) generic array field that allows reordering and renders
the child property as nodes.
- [`StorageUploadFieldBinding`](https://firecms.co/docs/../api/functions/StorageUploadFieldBinding) if the `of` property is a `string` with storage configuration.
- [`ArrayOfReferencesFieldBinding`](https://firecms.co/docs/../api/functions/ArrayOfReferencesFieldBinding) if the `of` property is a `reference`
- [`BlockFieldBinding`](https://firecms.co/docs/../api/functions/BlockFieldBinding) if the `oneOf` property is specified
Links:
- [API](https://firecms.co/docs/../api/interfaces/ArrayProperty)
## Map
In a map property you define child properties in the same way you define them
at the entity schema level:
```tsx
const ctaProperty = buildProperty({
dataType: "map",
properties: {
name: {
name: "Name",
description: "Text that will be shown on the button",
validation: { required: true },
dataType: "string"
},
type: {
name: "Type",
description: "Action type that determines the user flow",
validation: { required: true, uniqueInArray: true },
dataType: "string",
enumValues: {
complete: "Complete",
continue: "Continue"
}
}
}
});
```
#### `properties`
Record of properties included in this map.
#### `previewProperties`
List of properties rendered as this map preview. Defaults to first 3.
#### `spreadChildren`
Display the child properties as independent columns in the collection
view. Defaults to `false`.
#### `pickOnlySomeKeys`
Allow the user to add only some keys in this map.
By default, all properties of the map have the corresponding field in
the form view. Setting this flag to true allows to pick only some.
Useful for map that can have a lot of sub-properties that may not be
needed.
#### `expanded`
Determines whether the field should be initially expanded. Defaults to `true`.
#### `keyValue`
Render this map as a key-value table that allows to use
arbitrary keys. You don't need to define the properties in this case.
#### `minimalistView`
When set to `true`, displays the child properties directly without being wrapped in an extendable panel.
#### `validation`
* `required` Should this field be compulsory.
* `requiredMessage` Message to be displayed as a validation error.
---
The widget that gets created is
- [`MapFieldBinding`](https://firecms.co/docs/../api/functions/MapFieldBinding) Field that renders the children
property fields
Links:
- [API](https://firecms.co/docs/../api/interfaces/MapProperty)
## Geopoint
> *THIS PROPERTY IS CURRENTLY NOT SUPPORTED*
## Conditional fields from properties
When defining the properties of a collection, you can choose to use a builder
[`PropertyBuilder`](https://firecms.co/docs/api/type-aliases/PermissionsBuilder), instead of assigning the
property configuration directly.
This is useful for changing property configurations like available values on the
fly, based on other values.
:::tip
You can use property builders at any level of your property tree
(including children of maps an arrays).
You can access the complete values of the entity being edited in the builder
with the `values` prop, but also the value of the property being built with
`propertyValue`.
:::
#### Example 1
Example of field that gets enabled or disabled based on other values:
```tsx
type Product = {
name: string;
main_image: string;
available: boolean;
price: number;
related_products: EntityReference[];
publisher: {
name: string;
external_id: string;
}
}
export const productCollection: EntityCollection = buildCollection>({
name: "Product",
properties: {
available: {
dataType: "boolean",
name: "Available"
},
price: ({ values }) => ({
dataType: "number",
name: "Price",
validation: {
requiredMessage: "You must set a price between 0 and 1000",
min: 0,
max: 1000
},
disabled: !values.available && {
clearOnDisabled: true,
disabledMessage: "You can only set the price on available items"
},
description: "Price with range validation"
})
}
});
```
#### Example 2
A `User` type that has a `source` field that can be of type `facebook`
or `apple`, and its fields change accordingly
```tsx
type User = {
source: {
type: "facebook",
facebookId: string
} | {
type: "apple",
appleId: number
}
}
export const userSchema: EntityCollection = buildCollection({
name: "User",
properties: {
source: ({ values }) => {
const properties = buildProperties({
type: {
dataType: "string",
enumValues: {
"facebook": "FacebookId",
"apple": "Apple"
}
}
});
if (values.source) {
if ((values.source as any).type === "facebook") {
properties["facebookId"] = buildProperty({
dataType: "string"
});
} else if ((values.source as any).type === "apple") {
properties["appleId"] = buildProperty({
dataType: "number"
});
}
}
return ({
dataType: "map",
name: "Source",
properties: properties
});
}
}
});
```
## Custom fields
Custom fields let you fully control how a property's value is edited and displayed in a form. Instead of the built‑in renderer for a `dataType`, you supply a React component. That component receives a rich set of props (`FieldProps`) so it can:
- Read and update the current value (`value`, `setValue`)
- Update any other property in the same form (`setFieldValue` or `context.setFieldValue`)
- Access all current entity values + form utilities (`context`)
- Respect form state (`isSubmitting`, `disabled`, `showError`, `error`, `touched`)
- Adapt layout (`size`, `partOfArray`, `minimalistView`, `autoFocus`)
- Use developer defined `customProps`
### When should you create a custom field?
Use a custom field when you need one (or more) of the following:
- A visual style not covered by built‑ins (color pickers, tag inputs, sliders, charts, AI assisted fields, etc.)
- Composite UI combining several properties (e.g. lat/lng map picker writing to two numeric fields)
- Integrations (upload to an external API, fetch suggestions, geocode, etc.)
If you only need validation or simple transformation, prefer property level `validation` options first to keep things simple.
If you need dynamic behavior depending on other values, consider using [conditional fields](https://firecms.co/docs/conditional_fields.md) instead.
### Custom field example
A custom text field with a background color supplied via `customProps` (scroll below for full prop contract and advanced techniques):
```tsx
interface CustomColorTextFieldProps {
color: string;
}
export default function CustomColorTextField({
property,
value,
setValue,
customProps,
includeDescription,
showError,
error,
isSubmitting,
context
}: FieldProps) {
const { mode } = useModeController();
const backgroundColor = customProps?.color ?? (mode === "light" ? "#eef4ff" : "#16325f");
return (
<>
setValue(evt.target.value)}
/>
>
);
}
```
Usage in a collection:
```tsx
export const blogCollection = buildCollection({
id: "blog",
path: "blog",
name: "Blog entry",
properties: {
// ... other properties
gold_text: {
name: "Gold text",
description: "This field is using a custom component defined by the developer",
dataType: "string",
Field: CustomColorTextField,
customProps: {
color: "gold"
}
}
}
});
```
### Component contract (FieldProps)
Your component must at minimum:
1. Read the current `value` (it can be `undefined` or `null` for empty)
2. Call `setValue(newValue)` when the user changes it
Recommended good practices:
- Honor `disabled` / `isSubmitting`
- Show the label and error (use your own UI or `` / built‑ins)
- Avoid heavy side effects on every keystroke (debounce network calls)
Full interface: [`FieldProps`](https://firecms.co/docs/api/interfaces/FieldProps) (includes detailed comments).
### Passing custom props
Provide a `customProps` object in the property definition. The object is strongly typed via the second generic of `FieldProps`.
### Accessing the rest of the entity (form context)
`context` gives you live access to:
- All current values (`context.values`)
- `context.setFieldValue(key, value)` to update any other field
- `context.save(values)` to trigger a save programmatically (rarely needed in fields)
- Metadata: `entityId`, `status` (new/existing/copy), `collection`, `openEntityMode`, `disabled`
This enables cross‑field logic (e.g. auto‑fill slug when title changes) or conditional disabling.
### Rendering (or composing) other properties inside a custom field
If your custom field wants to include the UI of another property, use `PropertyFieldBinding`.
This keeps validation and consistency:
```tsx
```
This is ideal for composite widgets that orchestrate multiple underlying values.
For example, the built-in `map` default widget is just a wrapper around the properties defined
### Handling arrays & nested data
When your custom field is inside an array:
- `partOfArray` is `true`
- You may receive an index in a parent context when building nested array editors
For nested values (e.g. editing `address.street` inside a composite field), call `setFieldValue("address.street", value)`.
### Validation strategies
Prefer declarative validation in the property config when possible. You can still implement client‑side
guards in the field (e.g. ignore invalid keystrokes) but allow the central validation to surface errors.
Common patterns:
- Trim on blur but preserve user typing: keep raw input in local state, call `setValue` with cleaned value on blur.
- Async validation (e.g. uniqueness): debounce the check, set a transient local error, do not block typing.
### Performance tips
- Debounce network or expensive computations (`useEffect` + `setTimeout` or a utility) instead of per‑keystroke.
- Memo heavy child components based on relevant props.
- Avoid storing large derived objects in state; derive them on render or memoize.
### Advanced example: Composite slug editor
Automatically generates a slug from the title, but allows manual override.
```tsx
function SlugField({ value, setValue, context, property, showError, error }: FieldProps) {
const title = context.values.title as string | undefined;
React.useEffect(() => {
if (!value && title) {
const auto = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
setValue(auto);
}
}, [title]);
return (
setValue(e.target.value)}
helperText={showError ? error : "Will auto-generate from Title if left empty"}
/>
);
}
```
### Using PropertyFieldBinding inside a composite field
```tsx
function GeoPointField({ context }: FieldProps) {
return (
{/* Could add a map picker that calls context.setFieldValue("lat", newLat) */}
);
}
```
### Troubleshooting & gotchas
- Value not updating: Ensure you call `setValue` (not mutate `value` directly) and that you don't shadow the `value` in local state without syncing.
- Error never shows: Remember `showError` gates visual display; `error` can exist while `showError` is false.
- Cross‑field updates ignored: Use the exact property key (e.g. `address.street`, array indexes like `items[0].price`).
- Field re-renders too often: Wrap heavy logic in `useMemo` / `useCallback`, avoid creating new objects every render.
- Need read‑only mode: Respect `disabled` from props or `context.disabled`.
### Next steps
- Explore other customization: [Custom previews](https://firecms.co/docs/custom_previews.md)
- Reuse logic across many properties: create a shared field component and pass different `customProps`.
- Open source friendly? Consider contributing a reusable field to the community.
By leveraging custom fields you can create rich authoring experiences closely aligned with your product's domain while keeping validation, state and persistence centralized in FireCMS.
## Custom previews
Every property you define in the CMS has a preview component associated by
default. In some cases you may want to build a custom preview component.
Just as you can customize how your property field is rendered, you can change
how the preview of a property is **displayed in collection** and other **read only
views**.
You can build your preview as a React component that takes
[PropertyPreviewProps](https://firecms.co/docs/api/interfaces/PropertyPreviewProps) as props.
`PropertyPreviewProps` has two generic types: the first one is the type of the
property, such as `string` or `boolean` and the second one (optional) is the
type for any custom props you would like to pass to the preview, just like
done when defining custom fields.
#### Example
Example of a custom preview for a `boolean` property:
```tsx
export default function CustomBooleanPreview({
value, property, size
}: PropertyPreviewProps
)
{
return (
value ? :
);
}
```
...and how it is used:
```tsx
export const blogCollection = buildCollection({
name: "Blog entry",
properties: {
// ...
reviewed: {
name: "Reviewed",
dataType: "boolean",
Preview: CustomBooleanPreview
},
}
});
```
## Reusing property configurations
:::tip
When you define a property config, you will be able to select it in
the collection editor
:::
FireCMS 3 introduced a new way of defining properties that allows you to reuse
them across different entities and collections.
You can define a `propertyConfigs` object that
contains all the configurations related to a property. This is an array of
`PropertyConfig` objects, which are defined as follows:
```typescript
export type PropertyConfig = {
/**
* Key used to identify this property config.
*/
key: string,
/**
* Name of this field type.
* This is not the name of the property.
*/
name: string;
/**
* Default config for the property.
* This property or builder will be used as the base values for the resulting property.
* You can also use a builder function to generate the base property.
* You can use a builder function to generate the property based on the values or the path.
* You can also define a custom Field as a React component to be used for this property.
*/
property: PropertyOrBuilder;
/**
* Optional icon to be used in the field selector.
* Use a 24x24 component, in order not to break the layout.
* Any FireCMS icon can be used.
*/
Icon?: React.ComponentType;
/**
* CSS color, used only in some plugins like the field selector.
* e.g. "#2d7ff9"
*/
color?: string;
/**
* Description of this field type.
*/
description?: string;
}
```
Note that you can use any of the existing builders or properties as a base for
your custom property. What you define in your property will be used as a base
for the resulting property (the user is still able to customize it).
#### FireCMS Cloud
Let's define a custom property that consists of a translations map object with different string values:
```typescript
export const appConfig: FireCMSAppConfig = {
version: "1",
collections: [
// ...
],
propertyConfigs: [
{
name: "Translated string",
key: "translated_string",
property: {
dataType: "map",
properties: {
en: {
dataType: "string",
name: "English"
},
es: {
dataType: "string",
name: "Español"
},
},
},
}
]
};
```
#### FireCMS PRO
In FireCMS PRO, you can define the `propertyConfigs` in the `FireCMS` component:
```tsx
```
## Top level views
If you need to develop a custom view that does not map directly to a datasource
collection you can implement it as a React component. This is useful for
implementing custom dashboards, data visualizations, or any other custom
functionality you need.
The top level views can be customized based on the logged in user, so you can
implement custom views for different user roles, or even hide them completely
for unauthenticated users.
You can use all the components and hooks provided by FireCMS.
Check the [components gallery](https://firecms.co/docs/components) for more information.
You can also use all the hooks, including authentication, navigation, datasource (Firestore),
storage, etc.
You can also include collection views inside your custom views, or use the side panel
to see entity details, or use with completely custom components.
You need to define the name, route and the component, and add it to the main
navigation, as the example below.
By default, it will show in the main navigation view.
For custom views you can define the following props:
* `path` string
CMS Path you can reach this view from.
If you include multiple paths, only the first one will be included in the
main menu
* `name`: string
Name of this view
* `description`?: string
Optional description of this view. You can use Markdown
* `hideFromNavigation`?: boolean
Should this view be hidden from the main navigation panel.
It will still be accessible if you reach the specified path
* `view`: React.ReactNode
Component to be rendered. This can be any React component, and can use any
of the provided hooks
* `group`?: string
Optional field used to group top level navigation entries under a
navigation view.
#### Example self-hosted FireCMS
For self-hosted FireCMS, you can define your custom top level views by adding them in your `useBuildNavigationController` component.
You can pass them as an array of `CMSView` objects or a callback function that returns an array of `CMSView` objects.
In the callback, you can use the `user` object to conditionally show or hide views based on the user role, as well
as access the `authentication` and `navigation` hooks.
```tsx
// rest of you main app code
const customViews: CMSView[] = useMemo([{
path: "additional",
name: "Additional view",
description: "This is an example of an additional view that is defined by the user",
// This can be any React component
view:
}], []);
const navigationController = useBuildNavigationController({
views: ({ user }) =>{
if(!user) {
return [];
}
return customViews;
},
collections,
authController,
dataSourceDelegate: firestoreDelegate
});
```
#### Example FireCMS Cloud
In FireCMS Cloud, you can define your custom top level views by adding them to your `FireCMSAppConfig`. You can
use a callback or add them directly to the `views` property. The callback includes the logged in `user`, so you can
use it to conditionally show or hide views based on the user role.
```tsx
import { buildCollection, CMSView } from "@firecms/core";
import { ExampleCMSView } from "./ExampleCMSView";
import { FireCMSAppConfig, FireCMSCloudApp } from "@firecms/cloud";
const projectId = "YOUR_PROJECT_ID";
const customViews: CMSView[] = [{
path: "additional",
name: "Additional view",
description: "This is an example of an additional view that is defined by the user",
// This can be any React component
view:
}];
const productCollection = buildCollection({
name: "Product",
id: "products",
path: "products",
properties: {
name: {
name: "Name",
validation: { required: true },
dataType: "string"
}
}
});
const appConfig: FireCMSAppConfig = {
version: "1",
collections: ({ user }) => [
productCollection
],
views: ({ user }) => customViews
};
export default function App() {
return ;
}
```
#### Example Additional view
Your custom view is implemented as any regular React component.
You can use any of the provided hooks, including authentication, navigation, datasource, storage, app stage, etc.
```tsx
import React from "react";
import {
buildCollection,
Entity,
EntityCollectionView,
useAuthController,
useReferenceDialog,
useSelectionController,
useSideEntityController,
useSnackbarController
} from "@firecms/core";
import { Button, GitHubIcon, IconButton, Paper, Tooltip, Typography, } from "@firecms/ui";
const usersCollection = buildCollection({
path: "users",
id: "users",
name: "Users",
singularName: "User",
group: "Main",
description: "Registered users",
textSearchEnabled: true,
icon: "Person",
properties: {
first_name: {
name: "First name",
dataType: "string"
},
last_name: {
name: "Last name",
dataType: "string"
},
email: {
name: "Email",
dataType: "string",
email: true
},
phone: {
name: "Phone",
dataType: "string"
},
liked_products: {
dataType: "array",
name: "Liked products",
description: "Products this user has liked",
of: {
dataType: "reference",
path: "products"
}
},
picture: {
name: "Picture",
dataType: "map",
properties: {
large: {
name: "Large",
dataType: "string",
url: "image"
},
thumbnail: {
name: "Thumbnail",
dataType: "string",
url: "image"
}
},
previewProperties: ["large"]
}
},
additionalFields: [
{
key: "sample_additional",
name: "Sample additional",
Builder: ({ entity }) => <>{`Generated column: ${entity.values.first_name}`}>,
dependencies: ["first_name"]
}
]
});
/**
* Sample CMS view not bound to a collection, customizable by the developer
*/
export function ExampleCMSView() {
// hook to display custom snackbars
const snackbarController = useSnackbarController();
const selectionController = useSelectionController();
console.log("Selection from ExampleCMSView", selectionController.selectedEntities);
// hook to open the side dialog that shows the entity forms
const sideEntityController = useSideEntityController();
// hook to do operations related to authentication
const authController = useAuthController();
// hook to open a reference dialog
const referenceDialog = useReferenceDialog({
path: "products",
onSingleEntitySelected(entity: Entity | null) {
snackbarController.open({
type: "success",
message: "Selected " + entity?.values.name
})
}
});
const customProductCollection = buildCollection({
id: "custom_product",
path: "custom_product",
name: "Custom products",
properties: {
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
very_custom_field: {
name: "Very custom field",
dataType: "string"
}
}
});
const githubLink = (
);
return (
This is an example of an additional view
{authController.user
? <>Logged in as {authController.user.displayName}>
: <>You are not logged in>}
Use this button to select an entity under the path `products` programmatically
Use this button to open a snackbar
Use this button to open an entity in a custom path with a custom schema
You can include full entity collections in your views:
{githubLink}
);
}
```
## useAuthController
:::note
Please note that in order to use these hooks you **must** be in
a component (you can't use them directly from a callback function).
Anyhow, callbacks usually include a `FireCMSContext`, which includes all
the controllers.
:::
### `useAuthController`
Hook for accessing the authentication state and performing auth-related operations.
Works with any backend (Firebase, MongoDB, or custom implementations).
The props provided by this hook are:
* `user` The currently logged-in user object, or `null` if not authenticated
* `initialLoading` Initial loading flag, used to avoid showing login screen before auth state is determined
* `authLoading` Is the login/logout process ongoing
* `signOut()` Sign out the current user
* `authError` Error during authentication initialization
* `authProviderError` Error dispatched by the auth provider
* `getAuthToken()` Retrieve the auth token for the current user (returns a Promise)
* `loginSkipped` Has the user skipped the login process
* `extra` Additional data stored in the auth controller (useful for roles, permissions, etc.)
* `setExtra(extra)` Set additional data in the auth controller
* `setUser(user)` Programmatically set the current user (optional, implementation-dependent)
* `setUserRoles(roles)` Set user roles (optional, implementation-dependent)
Example:
```tsx
export function ExampleCMSView() {
const authController = useAuthController();
if (authController.authLoading) {
return Loading...;
}
return (
authController.user ?
Logged in as {authController.user.displayName}
:
You are not logged in
);
}
```
## useSideEntityController
:::note
Please note that in order to use these hooks you **must** be in
a component (you can't use them directly from a callback function).
Anyhow, callbacks usually include a `FireCMSContext`, which includes all
the controllers.
:::
You can use this controller to open the side entity view used to edit entities.
This is the same controller the CMS uses when you click on an entity in a collection
view.
Using this controller you can open a form in a side dialog, also if the path and
entity schema are not included in the main navigation defined in `FireCMS`.
The props provided by this hook are:
* `close()` Close the last panel
* `sidePanels` List of side entity panels currently open
* `open (props: SideEntityPanelProps)`
Open a new entity sideDialog. By default, the schema and configuration of the
view is fetched from the collections you have specified in the navigation. At
least you need to pass the path of the entity you would like to
edit. You can set an entityId if you would like to edit and existing one
(or a new one with that id).
Example:
```tsx
export function ExampleCMSView() {
const sideEntityController = useSideEntityController();
// You don't need to provide a schema if the collection path is mapped in
// the main navigation
const customProductCollection = buildCollection({
name: "Product",
properties: {
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
}
});
return (
);
}
```
## useSnackbarController
:::note
Please note that in order to use these hooks you **must** be in
a component (you can't use them directly from a callback function).
Anyhow, callbacks usually include a `FireCMSContext`, which includes all
the controllers.
:::
Use this hook to get a snackbar controller to display snackbars, with a message,
a type and an optional title.
The props provided by this hook are:
* `isOpen` Is there currently an open snackbar
* `close()` Close the currently open snackbar
* `open ({ type: "success" | "info" | "warning" | "error"; title?: string; message: string; })`
Display a new snackbar. You need to specify the type and message. You can
optionally specify a title
Example:
```tsx
export function ExampleCMSView() {
const snackbarController = useSnackbarController();
return (
);
}
```
## useReferenceDialog
:::note
Please note that in order to use this hook you **must** be in a
component (you can't use them directly from a callback function).
:::
### `useReferenceDialog`
This hook is used to open a side dialog that allows the selection of entities
under a given path. You can use it in custom views for selecting entities. You
need to specify the path of the target collection at least. If your collection
is not defined in your top collection configuration
(in your `FireCMS` component), you need to specify explicitly. This is the same
hook used internally when a reference property is defined.
The props provided by this hook are:
* multiselect?: boolean;
Allow multiple selection of values
* collection?: EntityCollection;
Entity collection config
* path: string;
Absolute path of the collection.
May be not set if this hook is being used in a component and the path is
dynamic. If not set, the dialog won't open.
* selectedEntityIds?: string[];
If you are opening the dialog for the first time, you can select some
entity ids to be displayed first.
* onSingleEntitySelected?(entity: Entity | null): void;
If `multiselect` is set to `false`, you will get the selected entity
in this callback.
* onMultipleEntitiesSelected?(entities: Entity[]): void;
If `multiselect` is set to `false`, you will get the selected entities
in this callback.
* onClose?(): void;
If the dialog currently open, close it
* forceFilter?: FilterValues;
Allow selection of entities that pass the given filter only.
Example:
```tsx
type Product = {
name: string;
price: number;
};
export function ExampleCMSView() {
// hook to display custom snackbars
const snackbarController = useSnackbarController();
// hook to open a reference dialog
const referenceDialog = useReferenceDialog({
path: "products",
onSingleEntitySelected(entity: Entity | null) {
snackbarController.open({
type: "success",
message: "Selected " + entity?.values.name
})
}
});
return ;
}
```
## useFireCMSContext
Get the context that includes the internal controllers and contexts used by the app.
Some controllers and context included in this context can be accessed
directly from their respective hooks.
The props provided by this hook are:
* `dataSource`: Connector to your database, e.g. your Firestore database
* `storageSource`: Used storage implementation
* `navigation`: Context that includes the resolved navigation and utility methods and
attributes.
* `sideEntityController`: Controller to open the side dialog displaying entity forms
* `sideDialogsController`: Controller used to open side dialogs (used internally by
side entity dialogs or reference dialogs)
* `dialogsController`: Controller used to open regular dialogs
* `authController`: Used auth controller
* `customizationController`: Controller holding the customization options for the CMS
* `snackbarController`: Use this controller to display snackbars
* `userConfigPersistence`: Use this controller to access data stored in the browser for the user
* `analyticsController`: Callback to send analytics events (optional)
* `userManagement`: Section used to manage users in the CMS. Used to show user info
in various places and assign entity ownership.
Example:
```tsx
export function ExampleCMSView() {
const context = useFireCMSContext();
// Access the data source
const dataSource = context.dataSource;
// Open a snackbar
context.snackbarController.open({
type: "success",
message: "Example message"
});
return Example view;
}
```
## useDataSource
Use this hook to access the data source being used in your FireCMS application.
This controller allows you to fetch and save data from your database (such
as Firestore or MongoDB) using the abstraction of collections and entities created by FireCMS.
:::note
Please note that in order to use this hook you **must** be in
a component (you can't use it directly from a callback function).
Anyhow, callbacks usually include a `FireCMSContext`, which includes all
the controllers including the `dataSource`.
:::
#### Available Methods
* `fetchCollection`: Fetch data from a collection
* `listenCollection`: Listen to entities in a given path with real-time updates
* `fetchEntity`: Retrieve an entity given a path and an id
* `listenEntity`: Get real-time updates on one entity
* `saveEntity`: Save an entity to the specified path
* `deleteEntity`: Delete an entity
* `checkUniqueField`: Check if the given property value is unique in the collection
* `generateEntityId`: Generate a new ID for an entity (optional, implementation dependent)
#### Example
```tsx
type Product = {
name: string;
price: number;
};
export function ProductLoader() {
const dataSource = useDataSource();
const [products, setProducts] = useState[]>([]);
useEffect(() => {
dataSource.fetchCollection({
path: "products",
limit: 10
}).then(setProducts);
}, [dataSource]);
return (
{products.map(product => (
{product.values.name}
))}
);
}
```
## useStorageSource
Use this hook to access the storage source being used in your FireCMS application.
Each file uploaded in FireCMS is referenced by a string in the form
`${path}/${fileName}`, which is then referenced in the datasource as a string
value in properties that have a storage configuration.
You can use this controller to upload files and get the storage path where it
was stored. Then you can convert that storagePath into a download URL.
:::note
Please note that in order to use this hook you **must** be in
a component (you can't use it directly from a callback function).
:::
#### Available Methods
* `uploadFile`: Upload a file, specifying the file, name, and path
* `getDownloadURL`: Convert a storage path into a download URL
#### Example
```tsx
export function FileUploader() {
const storageSource = useStorageSource();
const handleUpload = async (file: File) => {
const result = await storageSource.uploadFile({
file,
fileName: file.name,
path: "uploads",
});
console.log("File uploaded to:", result.path);
};
return (
{
if (e.target.files?.[0]) {
handleUpload(e.target.files[0]);
}
}}
/>
);
}
```
## useModeController
Use this hook to retrieve and control the current theme mode (`light`, `dark`, or `system`).
:::note
Please note that in order to use this hook you **must** be in
a component that is a child of the `FireCMS` component.
:::
#### Props
```tsx
{
mode: "light" | "dark";
setMode: (mode: "light" | "dark") => void;
}
```
#### Example
```tsx
export function ThemeToggle() {
const modeController = useModeController();
const toggleMode = () => {
modeController.setMode(modeController.mode === "light" ? "dark" : "light");
};
return (
);
}
```
## Building a blog in FireCMS Cloud

:::note
In this tutorial we assume you have set up a Firebase project and a FireCMS instance with custom code.
If you don't, check the [Quickstart](https://firecms.co/docs/cloud/quickstart) section.
:::
We don't need to explain the benefits of using a headless CMS instead of a
traditional blogging approach like WordPress, but here are a few:
- it is easier and faster to develop on, since there is no coupling between
frontend and backend.
- since your frontend is independent, you are free to change it in any way you
want, same goes for the backend.
- it is suited for omnichannel applications, you can use the same backend and
CMS with multiple apps and websites
- smaller, specialised teams
- scalability
- less costs
- flexibility and simplicity
:::tip[Frontend]
When you are done implementing the steps in this tutorial, you may want to check
how to build a simple **next.js** frontend app (or any other frontend framework).
We suggest next.js since it uses React, and it will make things easier.
We are already providing many of the React components you could need in
your frontend app, so you can focus on the business logic.
:::
### Let's build a blog with FireCMS
We will be building a collection that hosts blog posts. Each of the blog
posts will include a **dynamic array of elements**.
FireCMS has a built-in feature that allows you to build dynamic arrays. The
array property can be configured with the prop `oneOf`, to contain objects
that hold a specific type of value. This is perfect for building our blog entry
data structure!
These are the types that we will use:
- We define the type of the blog collection:
```tsx
type BlogEntry = {
name: string,
header_image: string,
created_on: Date,
status: string,
content: (BlogEntryImages | BlogEntryText | BlogEntryProducts)[];
}
```
- And each one of the types of the `content` array:
```tsx
type BlogEntryImages = {
type: "images";
value: string[];
}
type BlogEntryText = {
type: "text";
value: string;
}
type BlogEntryProducts = {
type: "products";
value: object[]; // We use a generic object here, but feel free to define a type for your products
}
```
#### Create the collection
:::tip
You can create this same collection using a template in the FireCMS dashboard.
:::
Let's start by initialising our collection, without any properties:
```tsx
export const blogCollection = buildCollection({
name: "Blog entry",
path: "blog",
properties: {}
});
````
#### Basic properties
Then we are going to add some simple properties for our entries.
- We want to have title, that must be always set, so we set the required
prop in validation to true:
```tsx
buildProperty({
name: "Title",
validation: { required: true },
dataType: "string"
})
```
- An image that will be on the top of the blog post:
```tsx
buildProperty({
name: "Header image",
dataType: "string",
storage: {
storagePath: "images",
acceptedFiles: ["image/*"],
metadata: {
cacheControl: "max-age=1000000"
}
}
})
```
- and a "created on" date that gets **autogenerated** when the document
is created.
```tsx
buildProperty( {
name: "Created on",
dataType: "date",
autoValue: "on_create"
})
```
:::note
You could also add a date field that gets updated whenever a document is saved,
with this configuration:
```tsx
buildProperty( {
name: "Updated on",
dataType: "date",
autoValue: "on_update"
})
```
:::
#### Conditional status field
Now we want to add a `status` string property that will have two possible values:
`published` and `draft`. We only want to allow the `published`
state when the rest of the fields are correct.
In this case we will keep it simple, and we will just check if the header image
is set:
```tsx
buildProperty(({ values }) => ({
name: "Status",
validation: { required: true },
dataType: "string",
columnWidth: 140,
enumValues: {
published: {
id: "published",
label: "Published",
disabled: !values.header_image,
},
draft: "Draft"
}
}))
```
#### Content of the blog entry
The content of our blog entries needs to be **dynamic**, so that content
managers are able to create complex entries with different components.
The content will be an array of objects, that will have a `type` attribute (that
will work as a discriminator), and a `value` attribute.
We will define 3 types:
- `images`: an array of images
- `text`: a Markdown text field
- `products`: an array of references to another collection, `products` in this
case.
We use the `oneOf` prop in array properties which is designed exactly for this
use case. You just need to define
```tsx
buildProperty({
name: "Content",
description: "Example of a complex array with multiple properties as children",
validation: { required: true },
dataType: "array",
columnWidth: 400,
oneOf: {
typeField: "type", // you can ommit these `typeField` and `valueField` props to use the defaults
valueField: "value",
properties: {
images: buildProperty({
name: "Images",
dataType: "array",
of: buildProperty({
dataType: "string",
storage: {
storagePath: "images",
acceptedFiles: ["image/*"],
metadata: {
cacheControl: "max-age=1000000"
}
}
}),
description: "This fields allows uploading multiple images at once and reordering"
}),
text: buildProperty({
dataType: "string",
name: "Text",
markdown: true
}),
products: buildProperty({
name: "Products",
dataType: "array",
of: {
dataType: "reference",
path: "products" // you need to define a valid collection in this path
}
})
}
}
})
```
This array configuration will create objects in the datasource with the format:
```json5
{
// ...
content: [
{
"type": "text",
"value": "Sunglasses or sun glasses (informally called shades or sunnies; more names below) are a form of protective eyewear designed primarily to prevent bright sunlight and high-energy visible light from damaging or discomforting the eyes. They can sometimes function as a visual aid, as variously employed for conditions such as light sensitivity, displays, and ultraviolet protection, and to improve visual clarity. In the early 20th century, they were also known as sun cheaters (or sun glasses) and were made from cardboard, tin, and other metals, with lenses made from crudely ground glass. They were also available in the form of goggles, with improvements in technology, sunglasses have become more Earth-friendly, and the use of plastics and other modern materials is now commonplace. "
},
{
"type": "images",
"value": [
"images/photo-1511499767150-a48a237f0083.jpeg",
"images/photo-1577803645773-f96470509666.jpeg"
]
},
{
"type": "text",
"value": "Sunglasses have long been associated with celebrities and film actors primarily from a desire to mask their identity. Since the 1940s, sunglasses have been popular as a fashion accessory, especially on the beach. "
},
{
"type": "products",
"value": [
{
"id": "B001UQ71F0",
"path": "products",
},
{
"id": "B001UQ71F0",
"path": "products",
}
]
}
],
// ...
}
```
#### Creating a preview view
Let's make use of another feature of FireCMS: **custom views for entities**!
FireCMS allows you to add additional views to your entity views, that are
defined as React components. The props you receive for building this
component are the entity collection, the original entity and the modified values.
In this case, we will create some React components to represent our blog entry
like the frontend app would do. This is the same code that you could
use in any SSR framework using React, such as `next.js`
You could also have more complex setup that sends your data to your SSR app
through an API and render the result.
You can find the code for `BlogEntryPreview` in
[BlogEntryPreview](https://github.com/firecmsco/firecms/blob/main/website/samples/samples_v3/recipes/blog/BlogEntryPreview.tsx).
When you have created your component, there are 2 ways you can add it to your component:
##### Register it in the CMS
The preferred way is to register it in the CMS, so that it is available in the view selector of
the collection.
In order to do so, you need to add it to the `entityViews` prop of the FireCMS main app config export:
```tsx
const appConfig: FireCMSAppConfig = {
version: "1",
collections: [],
entityViews: [
{
key: "blog_preview",
name: "Preview",
Builder: BlogEntryPreview
}
],
}
```
##### Register it in the collection
If you are defining your collection in code, you can also register it
in the collection itself:
```tsx
export const blogCollection = buildCollection({
name: "Blog entry",
path: "blog",
entityViews: [
{
key: "blog_preview",
name: "Preview",
Builder: BlogEntryPreview
}
],
properties: {
// ...
}
});
```
### Complete code:
If we put together all the parts that we have build in this tutorial,
we get the following code for the blog collection:
```tsx
import { buildCollection, buildProperty } from "@firecms/core";
import { BlogEntryPreview } from "./BlogEntryPreview";
import { BlogEntry } from "./types";
export const blogCollection = buildCollection({
name: "Blog entry",
id: "blog",
path: "blog",
entityViews: [{
key: "preview",
name: "Preview",
Builder: BlogEntryPreview
}],
properties: {
name: buildProperty({
name: "Name",
validation: { required: true },
dataType: "string"
}),
header_image: buildProperty({
name: "Header image",
dataType: "string",
storage: {
mediaType: "image",
storagePath: "images",
acceptedFiles: ["image/*"],
metadata: {
cacheControl: "max-age=1000000"
}
}
}),
content: buildProperty({
name: "Content",
description: "Example of a complex array with multiple properties as children",
validation: { required: true },
dataType: "array",
columnWidth: 400,
oneOf: {
typeField: "type", // you can ommit these `typeField` and `valueField` props to use the defaults
valueField: "value",
properties: {
images: buildProperty({
name: "Images",
dataType: "array",
of: buildProperty({
dataType: "string",
storage: {
mediaType: "image",
storagePath: "images",
acceptedFiles: ["image/*"],
metadata: {
cacheControl: "max-age=1000000"
}
}
}),
description: "This fields allows uploading multiple images at once and reordering"
}),
text: buildProperty({
dataType: "string",
name: "Text",
markdown: true
}),
products: buildProperty({
name: "Products",
dataType: "array",
of: {
dataType: "reference",
path: "products" // you need to define a valid collection in this path
}
})
}
}
}),
status: buildProperty(({ values }) => ({
name: "Status",
validation: { required: true },
dataType: "string",
columnWidth: 140,
enumValues: {
published: {
id: "published",
label: "Published",
disabled: !values.header_image
},
draft: "Draft"
},
defaultValue: "draft"
})),
created_on: buildProperty({
name: "Created on",
dataType: "date",
autoValue: "on_create"
})
}
})
```
## Add a slug to your documents
When building a CMS, it is common to want to generate slugs for your documents
based on a specific field, such as the title. This is useful for creating
SEO-friendly URLs and improving the discoverability of your content.
In this tutorial, we will show you how to automatically generate slugs for your
documents using FireCMS. It is very straightforward to add a callback that will
be executed before saving your document, allowing you to generate the slug based
on the title field.
#### Declare your collection
For illustrative purposes, let's create two simple `pages` collection, with just a title field, that we will
be adding a slug to:
```tsx
import { buildCollection } from "@firecms/core";
export type Page = {
title: string;
slug: string;
}
export const pagesCollection = buildCollection({
name: "Pages",
id: "pages",
path: "pages",
properties: {
title: {
name: "Title",
validation: { required: true },
dataType: "string"
},
slug: {
name: "Slug",
dataType: "string",
readOnly: true,
}
}
});
```
#### Add the pre-save callback
To add a slug to your documents, you can use the `onPreSave` callback provided by FireCMS.
This callback will be executed before saving the document, allowing you to modify the document before
it is written to the database.
```tsx
import { EntityCallbacks, slugify } from "@firecms/core";
const callbacks: EntityCallbacks = {
onPreSave: ({
collection,
path,
entityId,
values,
status,
context
}) => {
const updatedSlug = slugify(values.title);
values.slug = updatedSlug;
return values;
}
};
```
#### Complete code
Here is the complete code for this example:
```tsx
import { buildCollection, slugify } from "@firecms/core";
export type Page = {
title: string;
slug: string;
}
export const pagesCollection = buildCollection({
name: "Pages",
id: "pages",
path: "pages",
callbacks: {
onPreSave: ({
values,
}) => {
const updatedSlug = slugify(values.title);
values.slug = updatedSlug;
return values;
}
},
properties: {
title: {
name: "Title",
validation: { required: true },
dataType: "string"
},
slug: {
name: "Slug",
dataType: "string",
readOnly: true,
}
}
});
```
## Adapter for custom data types
:::info
In order to use this feature, you need to initialize a FireCMS repository. You can do this by following the
[Customization quickstart](https://firecms.co/docs/cloud/quickstart) guide.
:::
FireCMS allows you to customize the way data is displayed and edited in the CMS. By default, the views created by
FireCMS are based on the data type defined in your collection properties:
```json5
{
name: "Created on",
dataType: "date",
}
```
> In this example, all fields related to created on will be displayed as a date picker, including tables,
> forms, detail, and filter views.
However, you may want to use a different data type in the CMS than the one you use in your data model. For example, you
may want to save the date value as a Unix timestamp in your database, but display it as a date in the CMS.
To achieve this, you can use [callbacks](https://firecms.co/docs/collections/callbacks) to convert the data from your model to the format
you want to display in the CMS.
Let's use a simple example to illustrate this. Imagine you have an exercises collection, and you want to display the
creation date of each exercise in the CMS. In your database, you store the creation date as a Unix timestamp. You want
to display this date as a human-readable date in the CMS.
```typescript
export type Exercise = {
name: string;
createdOn: Date;
}
```
#### Converting data types when reading from the database
You can use the `onFetch` callback to convert the data type when reading from the database. For example, you can convert
a Unix timestamp to a date:
```typescript
const exerciseCallbacks: EntityCallbacks = {
onFetch({
collection,
context,
entity,
path
}: EntityOnFetchProps) {
const values = entity.values;
if (values.createdOn) {
values.createdOn = new Date(values.createdOn);
}
return entity;
}
};
```
#### Converting data types when saving to the database
You can use the `onPreSave` callback to convert the data type when saving to the database. In this case,
you can convert a date to a Unix timestamp:
```typescript
const exerciseCallbacks: EntityCallbacks = {
onPreSave: ({
collection,
path,
entityId,
values,
status,
context
}) => {
if (values.createdOn) {
values.createdOn = values.createdOn.getTime();
}
return values;
}
};
```
#### Putting it all together
You can use the `exerciseCallbacks` in your collection definition:
```typescript
const exerciseCallbacks: EntityCallbacks = {
onFetch({
collection,
context,
entity,
path
}: EntityOnFetchProps) {
const values = entity.values;
if (values.createdOn) {
values.createdOn = new Date(values.createdOn);
}
return entity;
},
onPreSave: ({
collection,
path,
entityId,
values,
status,
context
}) => {
if (values.createdOn) {
values.createdOn = values.createdOn.getTime();
}
return values;
}
};
// in order to use this collection with callbacks, add it to the collection definitions in the main app configuration
export const exerciseCollection: EntityCollection = buildCollection({
name: "Exercises",
path: "exercises",
callbacks: exerciseCallbacks,
properties: {
name: {
title: "Name",
dataType: "string",
validation: {
required: true
}
},
createdOn: {
title: "Created on",
dataType: "date", // this is the data type that will define the created views
validation: {
required: true
}
}
}
});
```
## Copying an entity from one collection to another

In this tutorial we will show how you can add a button to your collection,
that will allow you to **copy entities from another collection**.
It is very common in NoSQL databases, such as Firestore, to keep denormalized
data in different collections. That is, keeping the same information in multiple
locations, instead of normalized databases where information should be ideally
stored only once.
#### Declare your collections
For illustrative purposes, let's create two simple `products` collection,
that we will be copying from and to:
```tsx
import { buildCollection, buildProperties } from "@firecms/core";
export type Product = {
name: string;
price: number;
}
// Common properties of our target and source collections
export const properties = buildProperties({
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
price: {
name: "Price",
validation: {
required: true,
min: 0
},
dataType: "number"
}
});
// Source collection
export const productsCollection = buildCollection({
name: "Products",
id: "products",
path: "products",
properties
});
// Target collection
export const productsCollectionCopy = buildCollection({
name: "Products copy target",
id: "products_copied",
path: "products_copied",
properties
});
```
#### Add a custom action to your collection
For the next step we will add a custom button to our target collection.
This button will open a reference dialog and allow users to select an entity in
the source.
This example is using some hooks provided by FireCMS for developing
custom component.
```tsx
import { useCallback } from "react";
import { Entity, EntityCollection, useDataSource, useReferenceDialog, useSnackbarController } from "@firecms/core";
import { Button } from "@firecms/ui";
export type CopyEntityButtonProps = {
pathFrom: string;
pathTo: string;
collectionFrom: EntityCollection;
collectionTo: EntityCollection;
};
export function CopyEntityButton({
pathFrom,
collectionFrom,
pathTo,
collectionTo
}: CopyEntityButtonProps) {
// The datasource allows us to create new documents
const dataSource = useDataSource();
// We use a snackbar to indicate success
const snackbarController = useSnackbarController();
// We declare a callback function for the reference dialog that will
// create the new entity and show a snackbar when completed
const copyEntity = useCallback((entity: Entity | null) => {
if (entity) {
dataSource.saveEntity({
path: pathTo,
values: entity.values,
entityId: entity.id,
collection: collectionTo,
status: "new"
}).then(() => {
snackbarController.open({
type: "success",
message: "Copied entity " + entity.id
});
});
}
}, [collectionTo, dataSource, pathTo, snackbarController]);
// This dialog is used to prompt the selected collection
const referenceDialog = useReferenceDialog({
path: pathFrom,
collection: collectionFrom,
multiselect: false,
onSingleEntitySelected: copyEntity
});
return (
);
}
```
#### Add the custom copy action
After your component is ready, you can plug it into your collections `Actions`:
```tsx
import { buildCollection, CollectionActionsProps } from "@firecms/core";
import { CopyEntityButton } from "./copy_button";
import { Product, productsCollection, properties } from "./simple_product_collection";
export const productsCollectionCopy = buildCollection({
id: "products_copied",
name: "Products copy target",
path: "products_copied",
properties,
Actions: ({ path, collection }: CollectionActionsProps) =>
});
```
### Full code
```tsx
import { useCallback } from "react";
import {
buildCollection,
buildProperties,
CollectionActionsProps,
Entity,
EntityCollection,
useDataSource,
useReferenceDialog,
useSnackbarController
} from "@firecms/core";
import { Button } from "@firecms/ui";
type Product = {
name: string;
price: number;
}
type CopyEntityButtonProps = {
pathFrom: string;
pathTo: string;
collectionFrom: EntityCollection;
collectionTo: EntityCollection;
};
function CopyEntityButton({
pathFrom,
collectionFrom,
pathTo,
collectionTo
}: CopyEntityButtonProps) {
// The datasource allows us to create new documents
const dataSource = useDataSource();
// We use a snackbar to indicate success
const snackbarController = useSnackbarController();
// We declare a callback function for the reference dialog that will
// create the new entity and show a snackbar when completed
const copyEntity = useCallback((entity: Entity | null) => {
if (entity) {
dataSource.saveEntity({
path: pathTo,
values: entity.values,
entityId: entity.id,
collection: collectionTo,
status: "new"
}).then(() => {
snackbarController.open({
type: "success",
message: "Copied entity " + entity.id
});
});
}
}, [collectionTo, dataSource, pathTo, snackbarController]);
// This dialog is used to prompt the selected collection
const referenceDialog = useReferenceDialog({
path: pathFrom,
collection: collectionFrom,
multiselect: false,
onSingleEntitySelected: copyEntity
});
return (
);
}
// Common properties of our target and source collections
const properties = buildProperties({
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
price: {
name: "Price",
validation: {
required: true,
min: 0
},
dataType: "number"
}
});
// Source collection
export const productsCollection = buildCollection({
name: "Products",
id: "products",
path: "products",
properties
});
// Target collection
export const productsCollectionCopy = buildCollection({
name: "Products copy target",
id: "products_copied",
path: "products_copied",
properties,
Actions: ({ path, collection }: CollectionActionsProps) =>
});
```
## Dynamic root collections
Let's build a more complex example where the main navigation is **loaded dynamically** from
the database. We will use the `units` collection as the one for generating the
rest of the navigation.
For this example we will have `Units` and `Lessons` as the main content types,
imagine we are modeling the structure for a course.
In the units collection we will create a document for each unit:

And each of those documents will generate a new navigation item. In this case
we will have 3 navigation items, one for each unit:

#### Declare the main collection
Let's define the `units` collection as the main one:
:::note
We are going to implement a couple on callbacks on entity save and delete to
update the navigation when data is changed.
That prevents the suer from refreshing the app in order to see the changes.
How cool is that?
:::
```tsx
import { buildCollection } from "@firecms/core";
export type Unit = {
name: string;
description: string;
}
export const unitsCollection = buildCollection({
name: "Units",
singularName: "Unit",
group: "Main",
id: "units",
path: "units",
customId: true,
icon: "LocalLibrary",
callbacks: {
onSaveSuccess: ({ context }) => {
context.navigation.refreshNavigation();
},
onDelete: ({ context }) => {
context.navigation.refreshNavigation();
}
},
properties: {
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
description: {
name: "Description",
validation: { required: true },
dataType: "string",
multiline: true
}
}
});
```
#### Dynamic collection builder
Typically in FireCMS you pass a static list of collections to the main CMS
component, but in this case we need to build the collections dynamically based
on the data in the database.
FireCMS allows you to pass a function that returns a list of collections to the
`collections` prop of the `FireCMSApp` component.
```tsx
import { buildCollection, EntityCollectionsBuilder } from "@firecms/core";
import { Unit, unitsCollection } from "./unit_collection";
const collectionBuilder: EntityCollectionsBuilder = async ({
dataSource,
user
}) => {
const units = await dataSource.fetchCollection({
path: "units",
});
const lessonCollections = units.map(unit => buildCollection({
name: unit.values.name,
id: `units/${unit.id}/lessons`,
path: `units/${unit.id}/lessons`,
description: unit.values.description,
group: "Units",
properties: {
name: {
name: "Name",
dataType: "string"
},
description: {
name: "Description",
dataType: "string"
}
}
}));
return [
unitsCollection,
...lessonCollections
]
};
```
:::tip
Collections can be conveniently loaded asynchronously.
:::
This code is fetching the data that is being generated in the `units` collection
and creating a new collection for each of the documents.
### Full code
Wiring it all together we get a simple app that allows us to create new units
and lessons and navigate between them:
```tsx
import { buildCollection } from "@firecms/core";
import { FireCMSAppConfig } from "@firecms/cloud";
type Unit = {
name: string;
description: string;
}
const unitsCollection = buildCollection({
name: "Units",
singularName: "Unit",
group: "Main",
id: "units",
path: "units",
customId: true,
icon: "LocalLibrary",
callbacks: {
onSaveSuccess: ({ context }) => {
context.navigation.refreshNavigation();
},
onDelete: ({ context }) => {
context.navigation.refreshNavigation();
}
},
properties: {
name: {
name: "Name",
validation: { required: true },
dataType: "string"
},
description: {
name: "Description",
validation: { required: true },
dataType: "string",
multiline: true
}
}
});
const appConfig: FireCMSAppConfig = {
version: "1",
collections: async ({ dataSource }) => {
const units = await dataSource.fetchCollection({
path: "units",
});
const lessonCollections = units.map(unit => buildCollection({
name: unit.values.name,
id: `units/${unit.id}/lessons`,
path: `units/${unit.id}/lessons`,
description: unit.values.description,
group: "Units",
properties: {
name: {
name: "Name",
dataType: "string"
}
}
}));
return [
unitsCollection,
...lessonCollections
]
}
}
export default appConfig;
```
## How to use entity callbacks

In this tutorial we will create a simple collection, containing cars. Then, we are going to use the Entity Callbacks to can track changes in the entity, adding the timestamp of the last update and the user who made the change.
#### What are entity callbacks?
Entity callbacks are functions that are triggered at different stages of an entity's lifecycle in a database. They are particularly useful for:
- Data Validation: They can be used to validate data before it is saved to the database. This can help ensure that the data meets certain criteria or conforms to certain formats.
- Data Transformation: They can be used to transform data before it is saved or after it is fetched from the database. This can be useful for tasks like converting data types, formatting data, or adding additional fields.
- Tracking Changes: They can be used to track changes to entities. For example, they can be used to add metadata to an entity, such as the timestamp of the last update and the user who made the change.
- Implementing Custom Logic: They can be used to implement custom logic rules. For example, they can be used to prevent certain actions, like deleting entities that the user didn't create. You can use the auth modules and roles to define any logic.
- Auditing: They can be used to maintain an audit trail of changes made to the data. This can be useful for debugging and for maintaining data integrity.
In FireCMS we have been using these features in all versions, and it's also available in version 3.0.
#### Create a collection
For illustrative purposes, let's create a simple `cars` collection.
The interface of the collection will look like this:
```tsx
interface Car {
brand_name: string;
model_name: string;
fuel_type: "diesel" | "gas";
horse_power: number;
price_in_dollars: number;
modified_at?: Date;
modified_by?: string;
}
```
And the collection configuration will look like this:
```tsx
export const carsCollection = buildCollection({
id: "cars",
name: "Cars",
path: "car",
callbacks: carsCallbacks,
singularName: "Car",
properties: {
brand_name: buildProperty({
dataType: "string",
name: "Brand Name",
validation: {
required: true
},
enumValues: [
{
id: "alfa-romero",
label: "Alfa Romero"
},
{
id: "audi",
label: "Audi"
},
{
id: "bmw",
label: "Bmw"
},
{
label: "Mercedes Benz",
id: "mercedes-benz"
},
{
id: "porsche",
label: "Porsche"
}
]
}),
model_name: buildProperty({
dataType: "string",
name: "Model Name",
validation: {
required: true
}
}),
fuel_type: buildProperty({
validation: {
required: true
},
dataType: "string",
enumValues: [
{
label: "Diesel",
id: "diesel"
},
{
id: "gas",
label: "Gas"
},
{
id: "electric",
label: "Electric"
}
],
name: "Fuel type"
}),
horse_power: buildProperty({
validation: {
required: true
},
name: "Horse Power",
dataType: "number"
}),
price_in_dollars: buildProperty({
dataType: "number",
validation: {
required: true
},
name: "Price in Dollars"
}),
modified_at: buildProperty({
dataType: "date",
name: "Modified At",
validation: {
required: false
},
readOnly: true
}),
modified_by: buildProperty({
dataType: "string",
name: "Modified By",
validation: {
required: false
},
readOnly: true
})
}
});
```
Simple enough, right? Now let's add the callbacks.
#### Create the entity callbacks
Now, you can check the interface of the [EntityCallbacks in the documentation](/docs/collections/callbacks). We have all this callbacks available:
- `onFetch`: Called when an entity is fetched from the database.
- `onPreSave`: Called before an entity is saved to the database.
- `onSaveSuccess`: Called after an entity is saved successfully to the database.
- `onSaveFailure`: Called when an error occurs while saving an entity to the database.
- `onPreDelete`: Called before an entity is deleted from the database.
- `onDelete`: Called when an entity is deleted successfully from the database.
- `onIdUpdate`: Callback fired when any value in the form changes. You can use it to define the ID of the entity based on the current values
We want to save the user who made the last change and the date of the last change. We can use the `onPreSave` callback to achieve this. We are going use the builder util function `buildEntityCallbacks` to create the callbacks. This is helpful for creating the callbacks for a specific entity type. So we have the types inside the functions.
```tsx
const carsCallbacks = buildEntityCallbacks({
onPreSave: (entitySaveProps) => {
console.log("Callback onPreSave");
// We set the modified_at with the current timestamp
entitySaveProps.values.modified_at = new Date();
// We set the modified_by with the user who made the change using the displayName from the logged in user
entitySaveProps.values.modified_by = entitySaveProps.context.authController.user?.displayName ?? "Unknown User";
// We need to return the values
return entitySaveProps.values;
}
});
```
As you can see by the code, we are using a new Date to get the current date and time, and we are using the `authController` to get the user who is making the change. If the user is not logged in, we are going to use the string "Unknown User" as the user who made the change.
And then, we just need to update the collection config to include the callbacks:
```tsx
export const carsCollection = buildCollection({
id: "cars",
name: "Cars",
path: "car",
callbacks: carsCallbacks,
singularName: "Car",
properties: {
// ... properties
}
});
```
Now, when we save the entity, the `modified_at` and `modified_by` fields will be updated with the current date and the user who made the change.

Now let's implement another feature, let's block deleting the cars, or entities that you haven't created yourself; for that, we are gonna use the `onPreDelete` callback.
```tsx
const carsCallbacks = buildEntityCallbacks({
onPreDelete: (entityDeleteProps) => {
console.log("Callback onPreDelete");
if (entityDeleteProps.context.authController.user?.displayName !== entityDeleteProps.entity.modified_by) {
throw new Error("You cannot a car that wasn't created by yourself");
}
}
});
```
Now, when a user tries to delete a car that wasn't created by themselves, an error will be thrown.

So, we cannot delete the cars that we didn't create. In this example, the one created by the F1 driver Fernando Alonso.
### Full code
Full code available in the [FireCMS repository](https://github.com/marianmoldovan/firecms-entity-callbacks-cars)
## Icons
When building a UI, it's often necessary to include icons to represent different actions, states, or content.
FireCMS uses **Material Design icons**, which are available for free from Google. These icons are conveniently
packed as independent React components, which can be used in your own components.
In some cases you might need to use the icons as components, for example in a button. In other cases, you might
need to specify the icon key.
In this view you can see all the available icons and their keys. Just **search** for the icon you need!
## FireCMS UI
:::tip
All the components are exported from the `@firecms/ui` package. These are the same components used internally in **FireCMS**.
:::
**FireCMS UI** is a high quality set of components that you can use to build your own custom views. You can
use these components to build your own FireCMS views, or in any other React application. You just need to install
`tailwindcss` and the `@firecms/ui` package.
:::note[Why build this UI kit?]
FireCMS was using MUI until version 3.0. MUI provides ready to use components with intuitive APIs, but it also
comes with a lot of complexity and overhead. We wanted to build a simpler and more flexible UI kit that could be used
in any React project, not just in FireCMS.
We also wanted to make it easy to transition from MUI to our new UI kit, so we kept the API as similar as possible.
The result it a set of components that are easy to use, easy to customize, **much more performant** and with a smaller bundle size.
:::
The components are primarily built using **Radix UI** primitives and **tailwindcss**. This means that you can easily customize them
using tailwindcss classes or override the styles using CSS.
:::note
If you have any questions, feel free to reach us at our [Discord channel](https://discord.gg/fxy7xsQm3m).
:::
The list of components includes:
and more (check the sidebar for the full list).
### Installation
If you are using FireCMS, you don't need to install this package, as it is already included, and
configured for you.
To use the components in your own project, you need to install the `@firecms/ui` package:
```bash
yarn add @firecms/ui
```
or
```bash
npm install @firecms/ui
```
You also need to install `tailwindcss`:
```bash
npm install tailwindcss @tailwindcss/typography
```
or
```bash
yarn add tailwindcss @tailwindcss/typography
```
And initialize it in your project:
```bash
npx tailwindcss init
```
And add then add the fireCMS preset in your `tailwind.config.js`:
```javascript
export default {
presets: [fireCMSConfig],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@firecms/**/src/**/*.{js,ts,jsx,tsx}"
]
};
```
(You might need to adjust the paths in the `content` array to match your project structure)
Finally, you need to define your primary and secondary colors in your `index.css` file:
```css
@import "@firecms/ui/index.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-primary: #0070F4;
--color-secondary: #FF5B79;
}
```
## Alert
Alerts are used to communicate a state or feedback to users. They often indicate success, information, warning, or errors.
### Color
The `color` prop is used to define the severity level of the alert.
```tsx
import React from "react";
import { Alert } from "@firecms/ui";
export default function AlertColorDemo() {
return (
This is a simple alert.
This is an error alert.
This is a warning alert.
This is an info alert.
This is a success alert.
);
}
```
### Size
The `size` prop is used to define the size of the alert.
```tsx
import React from "react";
import { Alert } from "@firecms/ui";
export default function AlertSieDemo() {
return (
This is an small alert.
This is a medium alert.
This is a large alert.
);
}
```
### Dismissable
Alerts can be dismissable when provided with the `onDismiss` callback function.
```tsx
import React, { useState } from "react";
import { Alert } from "@firecms/ui";
export default function DismissableAlertDemo() {
const [visible, setVisible] = useState(true);
return (
<>
{visible && (
setVisible(false)} color="info">
This alert can be dismissed with the close button.
)}
>
);
}
```
### Action Button
Include an interactive element within the alert using the `action` prop.
```tsx
import React from "react";
import { Alert, Button } from "@firecms/ui";
export default function AlertActionButtonDemo() {
return (
Undo}
>
This alert contains an action button.
);
}
```
### Custom Styling
Pass custom CSS classes or styles with `className` and `style` props to customize the alert.
```tsx
import React from "react";
import { Alert } from "@firecms/ui";
export default function CustomStyleAlertDemo() {
return (
This alert has custom styling.
);
}
```
## Avatar
Avatars are visual placeholders for representing users or entities. They can contain images or initials and are commonly used in headers, lists, and anywhere user information is presented.
### Image Avatar
The `src` prop specifies the image URL for the avatar.
```tsx
import React from "react";
import { Avatar } from "@firecms/ui";
export default function AvatarImageDemo() {
return (
);
}
```
### Text Avatar
When the `src` prop is not provided, the avatar can display text such as user initials.
```tsx
import React from "react";
import { Avatar } from "@firecms/ui";
export default function AvatarTextDemo() {
return (
AB
);
}
```
### Custom Styling
The `className` prop allows you to pass custom CSS classes to the avatar component.
```tsx
import React from "react";
import { Avatar } from "@firecms/ui";
export default function CustomStyleAvatarDemo() {
return (
CD
);
}
```
## Badge
Badges are small counts and labeling components used to add additional information to any content. They're commonly used to display unread counts, status indicators, or just as decorative nodes.
### Basic Badge
By setting the `invisible` prop to `true`, you can hide the badge, making it not visible to users.
```tsx
import React from "react";
import { Badge, Button, Chip } from "@firecms/ui";
export default function BadgeInvisibleDemo() {
const [visible, setVisible] = React.useState(true);
return (
<>
Content with Badge
>
);
}
```
### Color Variants
The `color` prop determines the color of the badge. Possible values are `primary`, `secondary`, and `error`.
```tsx
import React from "react";
import { Badge, Chip } from "@firecms/ui";
export default function BadgeColorDemo() {
return (
<>
Primary color
Secondary color
Error color
>
);
}
```
### Usage with Icons and Buttons
Badges can be wrapped around icons or buttons to provide status indicators.
```tsx
import React from "react";
import { AnchorIcon, Badge, Button, IconButton } from "@firecms/ui";
export default function BadgeIconDemo() {
return (
<>
>
);
}
```
## BooleanSwitch
BooleanSwitch is a component for toggling between `true`, `false`, or an indeterminate state. It offers a binary choice or an optional third state, often signifying a lack of preference.
### Usage
Import the `BooleanSwitch` from `@firecms/ui` and provide the `value`, and optionally, a callback with `onValue-Change` to handle the state changes.
### Default Switch
Example of a simple switch that toggles between `true` and `false`.
```tsx
import React, { useState } from "react";
import { BooleanSwitch } from "@firecms/ui";
export default function BooleanSwitchDefaultDemo() {
const [value, setValue] = useState(true);
return (
);
}
```
### Indeterminate State
Example of a switch that toggles between `true`, `false`, and `null` (indeterminate).
```tsx
import React, { useState } from "react";
import { BooleanSwitch } from "@firecms/ui";
export default function BooleanSwitchIndeterminateDemo() {
const [value, setValue] = useState(null);
return (
);
}
```
### Size Variants
The `BooleanSwitch` component can have different sizes, controlled by the `size` prop.
```tsx
import React, { useState } from "react";
import { BooleanSwitch } from "@firecms/ui";
export default function BooleanSwitchSizeDemo() {
const [value, setValue] = useState(true);
return (
<>
>
);
}
```
### Disabled State
Disabled `BooleanSwitch` does not allow user interaction and appears visually distinct.
```tsx
import React from "react";
import { BooleanSwitch } from "@firecms/ui";
export default function BooleanSwitchDisabledDemo() {
return (
);
}
```
## Button
Buttons are interactive UI elements that allow users to trigger specific actions or events within an application.
They play a crucial role in user interfaces, providing a visual cue for user-initiated actions.
Buttons can be utilized in various contexts, including forms for submission, dialogs for confirmation,
and toolbars for quick access to functions and features. By clicking a button, users can submit data,
open new windows, execute commands, and much more, making buttons an essential component for driving user
interaction and engagement.
### Size
The prop `size` can be used to change the size of the button.
Buttons come in three sizes: `small`, `medium`, `large`, `xl` and `2xl`.
```tsx
import React from "react";
import { Button } from "@firecms/ui";
export default function ButtonSizeDemo() {
return (
);
}
```
### Variant
The `variant` prop changes the button's style. Possible values are `filled`, `outlined`, and `text`.
```tsx
import React from "react";
import { Button } from "@firecms/ui";
export default function VariantButtonDemo() {
return (
);
}
```
### Color
The `color` prop sets the color theme of the button. Possible values are `primary`, `secondary`, `text`, `error`, and `neutral`.
```tsx
import React from "react";
import { Button } from "@firecms/ui";
export default function ButtonColorDemo() {
return (
);
}
```
### Disabled
Setting `disabled` to `true` disables the button, preventing interactions.
```tsx
import React from "react";
import { Button } from "@firecms/ui";
export default function DisabledButtonDemo() {
return (
);
}
```
### Start Icon
The `startIcon` prop allows you to include an icon before the button's content.
```tsx
import React from "react";
import { AddIcon, Button } from "@firecms/ui";
export default function StartIconButtonDemo() {
return (
);
}
```
### Full Width
The `fullWidth` prop makes the button expand to take up the full width of its container.
```tsx
import React from "react";
import { Button } from "@firecms/ui";
export default function FullWidthButtonDemo() {
return (
);
}
```
### Custom Class Name
The `className` prop allows you to pass custom CSS classes to the button component.
```tsx
import React from "react";
import { Button } from "@firecms/ui";
export default function CustomClassNameButtonDemo() {
return (
)
}
```
### Button Component Props
- `variant`: Defines the style variant of the button. Options are `"filled"`, `"outlined"`, or `"text"`. Defaults to `"filled"`.
- `disabled`: Disables the button, preventing user interaction. Defaults to `false`.
- `color`: Sets the color theme of the button. Options are `"primary"`, `"secondary"`, `"neutral"`, `"text"`, or `"error"`.
- `size`: Specifies the size of the button. Options are `"small"`, `"medium"`, `"large"`, `"xl"`, or `"2xl"`. Defaults to `"medium"`.
- `startIcon`: Adds an icon at the start of the button content.
- `fullWidth`: When `true`, the button will expand to fill its container's width. Defaults to `false`.
- `className`: Additional classes to apply to the button element.
- `onClick`: Handler function called when the button is clicked.
- `children`: Defines the button content, typically text or elements.
- `component`: Custom component to be used for rendering the button.
- `type`: The type attribute for the button, typically `"button"`, `"submit"`, or `"reset"`. Defaults to `"button"`.
## Card
Cards are surfaces that display content and actions on a single topic. They should be easy to scan for relevant and actionable information. Cards can be used for a wide variety of purposes including to display information, as clickable actions, or as interactive elements of the UI.
### Usage
Import the `Card` from `@firecms/ui` and wrap the content you wish to display on the card. You can also make the card clickable by providing an `onClick` handler.
### Basic Card
Represents the basic usage of a card for displaying content.
```tsx
import React from "react";
import { Card } from "@firecms/ui";
export default function CardBasicDemo() {
return (
Content within a basic card.
);
}
```
### Clickable Card
Shows a card that has an onClick event, making it behave similar to a button.
```tsx
import React from "react";
import { Card } from "@firecms/ui";
export default function CardClickableDemo() {
const handleClick = () => {
console.log("Card clicked!");
};
return (
Clickable card content.
);
}
```
### Custom Styling
Demonstrates how additional classes or styles can be applied to the card for custom appearance.
```tsx
import React from "react";
import { Card } from "@firecms/ui";
export default function CardCustomStyleDemo() {
const styles = {
padding: '20px',
color: "red",
backgroundColor: '#f9f9f9',
borderRadius: '8px',
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)',
};
return (
Card with custom styling.
);
}
```
## CenteredView
CenteredView is a layout utility component that centers its children within the parent container. It's ideal for creating centered layouts with an optional maximum width.
### Usage
Import the `CenteredView` from `@firecms/ui` and wrap the content you wish to be centered. Optionally, specify a maximum width for the centered content or make it full screen.
### Basic Centered View
Demonstrates a centered view with default settings.
```tsx
import React from "react";
import { CenteredView } from "@firecms/ui";
export default function CenteredViewBasicDemo() {
return (
Basic centered view content.
);
}
```
### Max Width Centered View
Shows a centered view with a maximum width set.
```tsx
import React from "react";
import { CenteredView } from "@firecms/ui";
export default function CenteredViewMaxWidthDemo() {
return (
Centered view content with a maximum width set.
);
}
```
### Custom Styling Centered View
Illustrates how custom styles and classes can be applied to the centered view for a unique appearance.
```tsx
import React from "react";
import { CenteredView } from "@firecms/ui";
export default function CenteredViewCustomStyleDemo() {
return (
Centered view content with custom styling.
);
}
```
## Checkbox
Checkboxes are used for selection from multiple options. They can be toggled between checked, unchecked, and an intermediate state.
### Usage
To use the `Checkbox`, import it from your components and pass the `checked`, `onCheckedChange`, and optionally, `disabled`, `size`, and `color` props.
### Basic Checkbox
A simple checkbox with minimal configuration.
```tsx
import React, { useState } from "react";
import { Checkbox } from "@firecms/ui";
export default function CheckboxBasicDemo() {
const [checked, setChecked] = useState(true);
return (
setChecked(newChecked)}
/>
);
}
```
### Checkbox with Indeterminate State
A checkbox that showcases the indeterminate state, typically used for 'select all' scenarios where not all sub-selections are made.
```tsx
import React, { useState } from "react";
import { Checkbox } from "@firecms/ui";
export default function CheckboxIndeterminateDemo() {
const [indeterminate, setIndeterminate] = useState(true);
const [checked, setChecked] = useState(false);
return (
{
if (indeterminate) {
setIndeterminate(false);
setChecked(true);
} else if (checked) {
setChecked(false);
} else {
setIndeterminate(true);
}
}}
/>
);
}
```
### Checkbox Sizes
Illustrating how to use different sizes for the checkbox component.
```tsx
import React, { useState } from "react";
import { Checkbox } from "@firecms/ui";
export default function CheckboxSizeDemo() {
const [checked, setChecked] = useState(true);
return (
);
}
```
### Checkbox Colors
Demonstrates usage of the `color` prop to customize the checkbox appearance.
```tsx
import React, { useState } from "react";
import { Checkbox } from "@firecms/ui";
export default function CheckboxColorDemo() {
const [checkedPrimary, setCheckedPrimary] = useState(true);
const [checkedSecondary, setCheckedSecondary] = useState(true);
return (
);
}
```
## Chip
Chips are compact elements that represent an input, attribute, or action. They allow users to enter information, make selections, filter content, or trigger actions.
### Usage
To use the `Chip`, import it from your components and pass the necessary props like `children`, `colorScheme`, `size`, `error`, `outlined`, `onClick`, and `icon`.
### Basic Chip
A simple chip with minimal configuration. It displays a piece of text or a tag.
```tsx
import React from "react";
import { Chip } from "@firecms/ui";
export default function ChipBasicDemo() {
return (
Basic Chip
);
}
```
### Chip Sizes
Illustrating how to use different sizes for the chip component. You can choose between `tiny`, `small`, and `medium`.
```tsx
import React from "react";
import { Chip } from "@firecms/ui";
export default function ChipSizesDemo() {
return (
<>
Small Chip
Medium Chip
Large Chip
>
);
}
```
### Chip Colors
Demonstrates usage of the `colorScheme` prop to customize the chip appearance according to your application's theme.
```tsx
import React from "react";
import { Chip } from "@firecms/ui";
export default function ChipColorsDemo() {
return (
blueLighter
cyanLighter
tealLighter
greenLighter
yellowLighter
orangeLighter
redLighter
pinkLighter
purpleLighter
grayLighter
blueLight
cyanLight
tealLight
greenLight
yellowLight
orangeLight
redLight
pinkLight
purpleLight
grayLight
blueDark
cyanDark
tealDark
greenDark
yellowDark
orangeDark
redDark
pinkDark
purpleDark
grayDark
blueDarker
cyanDarker
tealDarker
greenDarker
yellowDarker
orangeDarker
redDarker
pinkDarker
purpleDarker
grayDarker
);
}
```
### Chip with Icon
Showcases how to use a chip with an icon for better user interaction or providing more information within the chip.
```tsx
import React from "react";
import { Chip, FaceIcon } from "@firecms/ui";
export default function ChipIconDemo() {
return (
}>
Chip with Icon
);
}
```
### Clickable Chip
This example demonstrates a clickable chip that triggers an action on click. Useful for interactive tags or selections.
```tsx
import React from "react";
import { Chip } from "@firecms/ui";
export default function ChipClickableDemo() {
const handleClick = () => {
console.log("Chip clicked");
};
return (
Clickable Chip
);
}
```
## CircularProgress
The `CircularProgress` component is used to indicate loading states to the user. It displays a spinning indicator which can be customized in size.
### Usage
To incorporate the `CircularProgress` component, import it from your component library and optionally pass the `size` and `className` props for customization.
### Basic CircularProgress
This demonstrates a basic usage of the `CircularProgress` component without any customization.
```tsx
import React from "react";
import { CircularProgress } from "@firecms/ui";
export default function CircularProgressBasicDemo() {
return ;
}
```
### CircularProgress Sizes
The following examples show how to use the `CircularProgress` component in different sizes: small, medium, and large.
```tsx
import React from "react";
import { CircularProgress } from "@firecms/ui";
export default function CircularProgressSizesDemo() {
return (
Smallest
Small
Medium
Large
);
}
```
## Collapse
Collapse components are used to show and hide content. React animation is used for the transition. Use it to toggle visibility of content.
### Usage
To use the `Collapse`, you need to import it from where it's defined, pass children to be displayed within the collapse component, and control its open state. Optionally, you can also customize its duration of the animation.
### Basic Collapse
A simple example of how to use the Collapse component.
```tsx
import React, { useState } from "react";
import { Button, Collapse, Paper } from "@firecms/ui";
export default function CollapseBasicDemo() {
const [isOpen, setIsOpen] = useState(true);
return (
Content to show or hide
);
}
```
### Custom Duration Collapse
Illustrates how to use a custom duration for the collapse animation.
```tsx
import React, { useState } from "react";
import { Button, Collapse, Container, Paper } from "@firecms/ui";
export default function CollapseCustomDurationDemo() {
const [isOpen, setIsOpen] = useState(false);
return (
This content has a custom animation duration.
);
}
```
## DateTimeField
DateTimeField is a versatile component allowing users to easily select dates and times. It can be configured for various modes such as date only or date-time selection, and it supports localization.
### Usage
`DateTimeField` component can be used to capture date or date-time values from users. It supports customization for disabling the field, clearing the selection, displaying errors, and more.
### Basic Usage
Provides a basic date-picker functionality where users can select a date.
```tsx
import React, { useState } from "react";
import { DateTimeField } from "@firecms/ui";
export default function DateTimeFieldBasicDemo() {
const [selectedDate, setSelectedDate] = useState(new Date());
return (
);
}
```
### Date-Time Selection
Enables selection of both date and time, suitable for scenarios where precise timing is crucial.
```tsx
import React, { useState } from "react";
import { DateTimeField } from "@firecms/ui";
export default function DateTimeFieldDateTimeDemo() {
const [selectedDateTime, setSelectedDateTime] = useState(new Date());
return (
);
}
```
### Localization
Showcases how to localize the DateTimeField component, adjusting it for different locales.
```tsx
import React, { useState } from "react";
import { DateTimeField } from "@firecms/ui";
export default function DateTimeFieldLocalizationDemo() {
const [selectedDate, setSelectedDate] = useState(new Date());
return (
);
}
```
## DebouncedTextField
`DebouncedTextField` is a variation of the standard `TextField` component designed to delay the invocation of the
`onChange` callback. This delay helps in reducing the number of `onChange` calls for inputs that may have
frequent updates, such as during typing.
### Usage
To use `DebouncedTextField`, import it from your components. It supports all the `TextField` props including `value`, `onChange` among others.
### Basic DebouncedTextField
This example shows a basic usage of the `DebouncedTextField`, demonstrating how it can be used to handle value changes with a defer mechanism to reduce the number of updates.
```tsx
import React, { useState } from "react";
import { DebouncedTextField } from "@firecms/ui";
export default function DebouncedTextFieldBasicDemo() {
const [value, setValue] = useState("");
const handleChange = (event: React.ChangeEvent) => {
setValue(event.target.value);
};
return (
);
}
```
## Dialog
Dialog components are used to present content in a layer above the app's main content, and they often request a user response. They are a critical component for modal dialogs, lightboxes, notification pop-ups, and custom content popups.
### Usage
To use the `Dialog`, import it from your components and pass the necessary props including `open`, `onOpenChange`, and the dialog's content as children. Optionally, you can customize its appearance with `className`, `fullWidth`, `fullHeight`, `fullScreen`, `scrollable`, `maxWidth`, `modal`, and `onOpenAutoFocus` props.
:::caution
You need to provide a `DialogTitle` in your `Dialog` component to ensure that the dialog is accessible.
You will see a warning in the console if you forget to provide a `DialogTitle`.
:::
### Basic Dialog
A basic example of using the dialog component to show a simple pop-up.
```tsx
import React, { useState } from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
SearchIcon,
TextField,
Typography
} from "@firecms/ui";
export default function DialogBasicDemo() {
const [open, setOpen] = useState(false);
return (
<>
>
);
}
```
### Full-Screen Dialog
An example of a dialog that covers the entire screen.
```tsx
import React, { useState } from "react";
import { Button, CenteredView, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
export default function DialogFullScreenDemo() {
const [open, setOpen] = useState(false);
return (
<>
>
);
}
```
### Scrollable Dialog
Illustrates how to make a dialog's content scrollable.
```tsx
import React, { useState } from "react";
import { Button, Dialog, DialogActions } from "@firecms/ui";
export default function DialogScrollableDemo() {
const [open, setOpen] = useState(false);
return (
<>
>
);
}
```
### Dialog with Custom Width
Demonstrates usage of the `maxWidth` prop to customize the dialog's width.
```tsx
import React, { useState } from "react";
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from "@firecms/ui";
export default function DialogCustomWidthDemo() {
const [open, setOpen] = useState(false);
return (
<>
>
);
}
```
## ExpandablePanel
`ExpandablePanel` is a versatile component that allows for content to be collapsible, enhancing the organization of UI by hiding content that is not immediately relevant to the user. This component can operate in controlled or uncontrolled mode, with additional features such as an invisible mode for a subtler UI, and an optional field mode to align with form field styling.
### Usage
To use the `ExpandablePanel`, import it from your components and provide the necessary props including `title`, `children`, and control props like `expanded`, `onExpandedChange`, and styling props such as `titleClassName`, `className`.
### Basic Expandable Panel
An example showing the basic usage of the `ExpandablePanel` component.
```tsx
import React, { useState } from "react";
import { ExpandablePanel } from "@firecms/ui";
export default function ExpandablePanelBasicDemo() {
const [expanded, setExpanded] = useState(false);
return (
Here is some content that was hidden but now is visible!
);
}
```
### Expandable Panel as Field
This variant showcases the `ExpandablePanel` utilized as a field in a form, demonstrating the combination of `asField` property.
```tsx
import React, { useState } from "react";
import { ExpandablePanel } from "@firecms/ui";
export default function ExpandablePanelFieldDemo() {
const [expanded, setExpanded] = useState(false);
return (
This Expandable Panel is styled as a field, making it a great choice for forms.
);
}
```
### Invisible Expandable Panel
An `ExpandablePanel` example where the panel borders are made invisible for a more seamless integration into the surrounding UI.
```tsx
import React, { useState } from "react";
import { ExpandablePanel } from "@firecms/ui";
export default function ExpandablePanelInvisibleDemo() {
const [expanded, setExpanded] = useState(false);
return (
This content is hidden inside an invisible panel, making the UI cleaner.
);
}
```
## File Upload
File Upload component is designed to easily handle the drag and drop of files as well as file selection through the dialog window. It supports features like file type restriction, maximum file size, custom titles, and descriptions.
### Usage
To use the `FileUpload`, import it from your components and pass the necessary props including `accept`, `onFilesAdded`, and optionally, `onFilesRejected`, `maxSize`, `disabled`, `maxFiles`, `title`, `uploadDescription`, `preventDropOnDocument`, and `size`.
### Basic File Upload
A simple file upload example with minimal configuration.
```tsx
import React from "react";
import { FileUpload } from "@firecms/ui";
export default function FileUploadBasicDemo() {
const onFilesAdded = (files: File[]) => {
console.log(files);
};
return (
);
}
```
### File Upload with Custom Types and Sizes
Demonstrating file upload with restrictions on file types and sizes, and custom messages.
```tsx
import React from "react";
import { FileUpload } from "@firecms/ui";
export default function FileUploadCustomDemo() {
const onFilesAdded = (files: File[]) => {
console.log("Files added", files);
};
const onFilesRejected = (fileRejections) => {
fileRejections.forEach(({ file, errors }) => {
console.error(`File ${file.name} was rejected:`, errors);
});
};
return (
);
}
```
### Disabled File Upload
Illustrating how to disable the file upload functionality.
```tsx
import React from "react";
import { FileUpload } from "@firecms/ui";
export default function FileUploadDisabledDemo() {
return (
{}}
title="Upload Disabled"
uploadDescription="File uploading is disabled"
disabled={true}
/>
);
}
```
## IconButton
IconButtons are versatile clickable elements that can be used in various parts of the interface. They support multiple sizes, shapes, and can be either filled or presented as ghost buttons.
### Usage
To use the `IconButton`, import it from your components and pass the required props such as `size`, `variant`, `shape`, `disabled`, `toggled`, and `onClick`.
### Basic IconButton
A simple icon button with minimal configuration.
```tsx
import React from "react";
import { AddIcon, IconButton } from "@firecms/ui";
export default function IconButtonBasicDemo() {
return (
console.log("Clicked!")}>
);
}
```
### IconButton Sizes
Demonstrates how to use different sizes for the IconButton component.
```tsx
import React from "react";
import { AddIcon, IconButton } from "@firecms/ui";
export default function IconButtonSizeDemo() {
return (
<>
console.log("Small Clicked!")}>
console.log("Medium Clicked!")}>
console.log("Large Clicked!")}>
>
);
}
```
### IconButton Shapes
Illustrating how to use different shapes (circular or square) for the IconButton component.
```tsx
import React from "react";
import { AddIcon, IconButton } from "@firecms/ui";
export default function IconButtonShapeDemo() {
return (
<>
console.log("Circular Clicked!")}>
console.log("Square Clicked!")}>
>
);
}
```
### IconButton Variants
Showing the different variants (ghost or filled) available for styling the IconButton.
```tsx
import React from "react";
import { AddIcon, IconButton } from "@firecms/ui";
export default function IconButtonVariantDemo() {
return (
<>
console.log('Ghost Clicked!')}>
console.log('Filled Clicked!')}>
>
);
}
```
## InfoLabel
InfoLabel is a versatile component used to display information or warning messages in different contexts. It leverages the flexibility of TailwindCSS for styling, offering predefined color modes for quick customization.
### Usage
To use `InfoLabel`, import it into your component and specify the `children` to display inside it. Optionally, you can also set the `mode` prop to change the appearance based on the context (`info` or `warn`).
### Basic Info Label
A basic example showing how to use the InfoLabel component to display an informational message.
```tsx
import React from "react";
import { InfoLabel } from "@firecms/ui";
export default function InfoLabelBasicDemo() {
return (
This is an informational message.
);
}
```
### Warning Label
Illustrating how to use the InfoLabel component to display a warning message by setting the mode to `warn`.
```tsx
import React from "react";
import { InfoLabel } from "@firecms/ui";
export default function InfoLabelWarnDemo() {
return (
Warning: This is a warning message.
);
}
```
## Label
The `Label` component is a simple and versatile component used to display text content with a label style.
You usually use it to display a label for an input field, like a checkbox, or radio button.
### Usage
To use the `Label` component, import it from your components. You can pass a `border` prop to add a border around the label.
You can also pass any of the HTML `label` props, such as `htmlFor`, `className`, and `style`.
### Label with a Checkbox
Simple example of using the `Label` component to create a basic surface for content.
```tsx
import React from "react";
import { Checkbox, Label } from "@firecms/ui";
export default function LabelCheckboxDemo() {
const [checked, setChecked] = React.useState(false);
return (
);
}
```
### Label with a Radio Button
This is an example of using the `Label` component with a radio button.
```tsx
import React from "react";
import { Label, RadioGroup, RadioGroupItem } from "@firecms/ui";
export default function LabelRadioButtonDemo() {
return (
);
}
```
## LoadingButton
The Loading Button component is utilized to display an actionable button with integrated loading feedback. This is useful to provide users immediate feedback on their actions that require asynchronous operations.
### Usage
To use the `LoadingButton`, import it along with necessary props. You can pass `loading`, `disabled`, `onClick`, `startIcon`, and other button props.
### Basic Loading Button
A simple loading button showcasing the loading state and default appearance.
```tsx
import React from "react";
import { LoadingButton } from "@firecms/ui";
export default function LoadingButtonBasicDemo() {
const [loading, setLoading] = React.useState(false);
const onClick = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 2000);
};
return (
Click Me
);
}
```
### Loading Button with Start Icon
A loading button example that includes a start icon which is displayed when the button is not in loading state.
```tsx
import React from "react";
import { AddIcon, LoadingButton } from "@firecms/ui";
export default function LoadingButtonWithIconDemo() {
const [loading, setLoading] = React.useState(false);
const onClick = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 2000);
};
return (
}
loading={loading}
onClick={onClick}>
Click Me
);
}
```
## Markdown
Markdown component allows rendering Markdown content with support for custom sizes and classes for personalized styling. It leverages the `markdown-it` library for conversion and supports HTML content within markdown.
### Usage
To use the `Markdown` component, import it from @firecms/ui and pass the `source` prop with the markdown content you want to display. Optionally, you can specify the `size` and `className` props to adjust the appearance.
### Basic Markdown
A simple markdown rendering example.
```tsx
import React from "react";
import { Markdown } from "@firecms/ui";
const markdownSource = `
## Markdown Example
This is a basic Markdown rendering.
- Bullet one
- Bullet two
`;
export default function MarkdownBasicDemo() {
return ;
}
```
### Markdown Sizes
Demonstrates how to adjust the size of the markdown text using the `size` prop.
```tsx
import React from "react";
import { Markdown } from "@firecms/ui";
const markdownSource = `
## Different Sizes
You can use the \`size\` prop to adjust the markdown size.
### Medium (default)
- Bullet one
- Bullet two
### Large
- Bullet one
- Bullet two
`;
export default function MarkdownSizeDemo() {
return (
<>
>
);
}
```
### Custom Styled Markdown
Illustrating the use of `className` prop to apply custom styles to the markdown component.
```tsx
import React from "react";
import { Markdown } from "@firecms/ui";
const markdownSource = `
## Custom Styled Markdown
You can apply custom styles using the \`className\` prop.
`;
export default function MarkdownCustomDemo() {
return ;
}
```
## Menu
The `Menu` component provides a flexible dropdown menu functionality. It leverages the `@radix-ui/react-dropdown-menu` for accessibility and customization features. The `Menu` component and its sub-component, `MenuItem`, can be easily styled and incorporated into your UI for various needs such as navigation menus, options menus, and more.
### Usage
The `Menu` component requires a `trigger` element to toggle the visibility of the menu and can accept any ReactNode as its children, which typically includes `MenuItem` components for the menu options.
### Basic Menu
A simple example of using the `Menu` component to create a dropdown menu.
```tsx
import React from "react";
import { Button, Menu, MenuItem } from "@firecms/ui";
export default function MenuBasicDemo() {
return (
);
}
```
### Controlled Menu
You can control the visibility of the `Menu` component by passing the `open` and `onOpenChange` props.
```tsx
import React from "react";
import { Button, Menu, MenuItem } from "@firecms/ui";
export default function MenuCustomTriggerDemo() {
const [open, setOpen] = React.useState(false);
return (
);
}
```
### Dense Menu Items
Showing how to make the `MenuItem` components dense for a more compact menu appearance.
```tsx
import React from "react";
import { Button, Menu, MenuItem } from "@firecms/ui";
export default function MenuDenseItemsDemo() {
return (
);
}
```
## Menubar
The Menubar component provides a customizable and accessible menu bar, built using Radix UI Menubar primitives. It can be used to create complex menu structures with various functionalities such as submenus, checkboxes, and radio groups.
### Usage
To use the `Menubar`, import it from your components and compose it with other related Menubar components like `MenubarMenu`, `MenubarTrigger`, `MenubarContent`, `MenubarItem`, etc.
### Menubar Example
A menubar showcasing all the basic functionalities of the Menubar component.
```tsx
import React from "react";
import {
FiberManualRecordIcon,
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarItemIndicator,
MenubarMenu,
MenubarPortal,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarSubTriggerIndicator,
MenubarTrigger
} from "@firecms/ui";
const RADIO_ITEMS = ["Andy", "Benoît", "Luis"];
const CHECK_ITEMS = ["Always Show Bookmarks Bar", "Always Show Full URLs"];
export default function MenubarDemo() {
const [checkedSelection, setCheckedSelection] = React.useState([CHECK_ITEMS[1]]);
const [radioSelection, setRadioSelection] = React.useState(RADIO_ITEMS[2]);
return (
File
New Tab{" "}
⌘ T
New Window{" "}
⌘ N
New Incognito Window
Share
Email Link
Messages
Notes
Print…{" "}
⌘ P
Edit
Undo{" "}
⌘ Z
Redo{" "}
⇧ ⌘ Z
Find
Search the web…
Find…
Find Next
Find Previous
Cut
Copy
Paste
View
{CHECK_ITEMS.map((item) => (
setCheckedSelection((current) =>
current.includes(item)
? current.filter((el) => el !== item)
: current.concat(item)
)
}
>
{item}
))}
Reload{" "}
⌘ R
Force Reload{" "}
⇧ ⌘ R
Toggle Fullscreen
Hide Sidebar
Profiles
{RADIO_ITEMS.map((item) => (
{item}
))}
Edit…
Add Profile…
);
};
```
## MultiSelect
MultiSelect allows users to select multiple options from a dropdown list. It supports opening and closing states, custom value rendering, and keyboard navigation among other features.
### Usage
To use the `MultiSelect`, import it alongside its item component. You can pass props like `value`, `onMultiValueChange`, `size`, `label`, `disabled`, and many more to customize its behavior and appearance.
### Basic MultiSelect
A simple example demonstrating the basic usage of the `MultiSelect` component.
```tsx
import * as React from "react";
import { MultiSelect, MultiSelectItem } from "@firecms/ui";
export default function MultiSelectBasicDemo() {
const [selectedValues, setSelectedValues] = React.useState([]);
return (
Option 1
Option 2
Option 3
);
}
```
### Custom Value Rendering
This example shows how to customize the rendering of selected values.
```tsx
import * as React from "react";
import { MultiSelect, MultiSelectItem } from "@firecms/ui";
export default function MultiSelectCustomRenderDemo() {
const [selectedValues, setSelectedValues] = React.useState([]);
return (
(values.map((value, index) =>
{value}
)
)}>
Red
Green
Blue
);
}
```
### Handling Disabled State
An example to demonstrate a `MultiSelect` component in a disabled state.
```tsx
import * as React from "react";
import { MultiSelect, MultiSelectItem } from "@firecms/ui";
export default function MultiSelectDisabledDemo() {
return (
(values.map((value) =>
{value}
)
)}>
Option 1
Option 2 is disabled
Option 3 is disabled
);
}
```
## Paper
The `Paper` component is a simple and versatile component used to display content within a flat or elevated surface. This makes it useful as a building block for various UI sections such as cards, dialogues, or panels.
### Usage
To use the `Paper` component, import it from your components. You can pass children, an optional `style`, and an optional `className` prop for additional styling.
### Basic Paper
Simple example of using the `Paper` component to create a basic surface for content.
```tsx
import React from "react";
import { Paper } from "@firecms/ui";
export default function PaperBasicDemo() {
return (
This is a basic paper component.
);
}
```
### Customized Paper
Illustrates how to customize the `Paper` component by passing `style` and `className` props.
```tsx
import React from "react";
import { Paper } from "@firecms/ui";
export default function PaperCustomizedDemo() {
const customStyle = {
padding: "20px",
backgroundColor: "#f5f5f5",
borderRadius: "4px",
boxShadow: "0 2px 4px rgba(0,0,0,.1)"
};
return (
This is a customized paper component.
);
}
```
## Popover
Popover component allows you to float a content panel anchored to another element, perfect for context menus, hover cards, tooltips, and much more.
### Usage
To use the `Popover`, import it from your components and pass the required `trigger` and optional `open`, `onOpenChange`, `side`, `sideOffset`, `align`, `alignOffset`, `arrowPadding`, `sticky`, `hideWhenDetached`, `avoidCollisions`, `enabled`, and `modal` props.
### Basic Popover
A simple popover that shows upon clicking the trigger element.
```tsx
import React from "react";
import { Button, Popover } from "@firecms/ui";
export default function PopoverBasicDemo() {
return (
Open Popover}
>
This is a basic Popover.
);
}
```
### Popover with Alignment
Showcasing how to align the popover content relative to the trigger element.
```tsx
import React from "react";
import { Button, Popover } from "@firecms/ui";
export default function PopoverAlignDemo() {
return (
Open Left}
side="left"
>
Aligned to the left.
Open Bottom}
side="bottom"
>
Aligned to the bottom.
);
}
```
### Controlled Popover
Demonstrates the usage of `open` and `onOpenChange` props for controlled behavior.
```tsx
import React, { useState } from "react";
import { Button, Popover } from "@firecms/ui";
export default function PopoverControlledDemo() {
const [open, setOpen] = useState(false);
return (
setOpen(!open)}>Toggle Popover}
open={open}
onOpenChange={setOpen}
>
This Popover's visibility is controlled externally.
);
}
```
### Usage with Custom Styling
Illustrating custom styling applied to the popover content.
```tsx
import React from "react";
import { Button, Popover } from "@firecms/ui";
export default function PopoverStyledDemo() {
return (
Open Custom Styled Popover}
className="bg-purple-500 text-white p-3 rounded-lg shadow-lg"
>
This Popover has custom styles applied.
);
}
```
## Radio Group
Radio Group allows users to select one option from a set. It's useful for exclusive selection scenarios where only one choice is permissible.
Each radio group item is represented by a `RadioGroupItem` component, and typically wrapped by a `Label` component.
### Usage
To use the `RadioGroup` and `RadioGroupItem`, import them from your components and structure your options using the `RadioGroup` as the container and `RadioGroupItem` for each option.
### Basic Radio Group
A basic example of a radio group, allowing for simple selection.
```tsx
import React from "react";
import { Label, RadioGroup, RadioGroupItem } from "@firecms/ui";
export default function RadioGroupBasicDemo() {
return (
console.log(value)}>
);
}
```
### Customizing Radio Group Item Appearance
This example demonstrates how to customize the appearance of individual radio group items.
```tsx
import React from "react";
import { Label, RadioGroup, RadioGroupItem } from "@firecms/ui";
export default function RadioGroupCustomDemo() {
return (
console.log(value)}>
);
}
```
### Disabled Radio Group
How to disable the entire radio group or individual items within it.
```tsx
import React from "react";
import { Label, RadioGroup, RadioGroupItem } from "@firecms/ui";
export default function RadioGroupDisabledDemo() {
return (
);
}
```
## SearchBar
The `SearchBar` component is designed for implementing search functionalities. It supports features like debouncing search input, expandable input sizes, and showing loading state.
### Usage
To use the `SearchBar`, import it from your components and pass the necessary props like `onTextSearch`, `placeholder`, `expandable`, `large`, `autoFocus`, `disabled`, `loading`, and `inputRef`.
### Basic SearchBar
The basic usage of `SearchBar` with minimal configuration.
```tsx
import React from "react";
import { SearchBar } from "@firecms/ui";
export default function SearchBarBasicDemo() {
return (
console.log("Search:", text)} />
);
}
```
### SearchBar with Loading State
A demonstration of the `SearchBar` showing a loading indicator.
```tsx
import React from "react";
import { SearchBar } from "@firecms/ui";
export default function SearchBarLoadingDemo() {
return (
);
}
```
### SearchBar Expandable
This example shows how to make the `SearchBar` expandable upon focusing.
```tsx
import React from "react";
import { SearchBar } from "@firecms/ui";
export default function SearchBarExpandableDemo() {
return (
);
}
```
### Large SearchBar
Showcases a larger variant of the `SearchBar`.
```tsx
import React from "react";
import { SearchBar } from "@firecms/ui";
export default function SearchBarLargeDemo() {
return (
);
}
```
## Select
Select is a form component that provides a dropdown menu for users to choose from among several options. It supports single and multiple selections, customizable styles, and integration with form libraries.
### Usage
To use the `Select` component in your web application, start by importing it along with `SelectItem` for individual
options and `SelectGroup` if you need to group related options together. The `Select` component can be highly
customized to fit your specific user interface requirements. This allows you to control its appearance, such as its
size, shape, and color, to match the overall design of your application. Additionally, you can modify its behavior,
including how it handles user interactions, whether it supports multiple selections, and how it displays selected items.
This level of customization makes it a versatile tool for creating intuitive and responsive dropdown menus
that improve user experience.
### Basic Select
A basic usage of the select component with minimal configuration.
```tsx
import React from "react";
import { Select, SelectItem } from "@firecms/ui";
export default function SelectBasicDemo() {
const [selected, setSelected] = React.useState();
return (
);
}
```
### Customized Select
A select component with custom styles and functionalities.
```tsx
import React from "react";
import { Chip, Select, SelectItem } from "@firecms/ui";
const beverages = {
coffee: "Coffee",
tea: "Tea",
juice: "Juice",
soda: "Soda",
water: "Water"
}
export default function SelectCustomDemo() {
const [selected, setSelected] = React.useState("");
return (
);
}
```
### Select with Groups
Demonstrates how to group options under labels using `SelectGroup`.
```tsx
import React from "react";
import { Select, SelectItem, SelectGroup } from "@firecms/ui";
export default function SelectGroupDemo() {
const [selected, setSelected] = React.useState("");
return (
);
}
```
### Select Component Props
The `Select` component in FireCMS UI is highly customizable through various props. Below is a comprehensive list of props you can use to tailor the `Select` component to your needs:
- `open`: Controls whether the select dropdown is open. Defaults to `false`.
- `name`: The name attribute for the select input element.
- `id`: The id attribute for the select input element.
- `onOpenChange`: Callback when the open state changes.
- `value`: The current value(s) of the select component, which can be a `string` or an array of `strings` for multiple selections.
- `className`: Additional classes to apply to the root element.
- `inputClassName`: Additional classes to apply to the input element.
- `onChange`: Handler function called when the select value changes.
- `onValueChange`: Callback when the value changes.
- `onMultiValueChange`: Callback when the value changes in a multiple select.
- `placeholder`: The placeholder text displayed when no value is selected.
- `renderValue`: Custom render function for the selected value.
- `renderValues`: Custom render function for the selected values in multiple select.
- `size`: The size of the select component, can be `"small"` or `"medium"`. Defaults to `"medium"`.
- `label`: The label displayed above the select field, can be a `ReactNode` or a `string`.
- `disabled`: Disables the select component. Defaults to `false`.
- `error`: Sets the select component in an error state. Defaults to `false`.
- `position`: Position of the dropdown relative to the trigger, can be `"item-aligned"` or `"popper"`. Defaults to `"item-aligned"`.
- `endAdornment`: Element to be placed at the end of the select input.
- `multiple`: Enables multiple selection mode. Defaults to `false`.
- `inputRef`: Ref object for the select input element.
- `padding`: Adds padding to the select input. Defaults to `true`.
- `invisible`: Hides the select component but keeps it in the DOM.
- `children`: Content to be rendered as the options within the select component.
## Separator
Separators are used to visually distinguish content in a layout or list. They can be either horizontal or vertical, making them versatile for various design needs.
### Usage
To use the `Separator`, import it from your components and specify the `orientation` and optionally, the `decorative` prop to control its appearance.
### Horizontal Separator
A separator that spans horizontally, useful for dividing content like list items or sections in a layout.
```tsx
import React from "react";
import { Separator } from "@firecms/ui";
export default function SeparatorHorizontalDemo() {
return ;
}
```
### Decorative Separator
Demonstrates how to use the `decorative` prop to render a separator that is meant for visual or decorative purposes rather than semantic division of content.
```tsx
import React from "react";
import { Separator } from "@firecms/ui";
export default function SeparatorDecorativeDemo() {
return ;
}
```
## Sheet
The `Sheet` component is used to display sliding panels from the edges of the screen. It can be opened from the top, bottom, left, or right, and may be either opaque or transparent.
### Usage
To use the `Sheet`, import it from your components and pass the `open`, `side`, `transparent`, and `onOpenChange` props.
### Basic Sheet
A simple sheet that slides in from the right.
```tsx
import React, { useState } from "react";
import { Button, Sheet } from "@firecms/ui";
export default function SheetBasicDemo() {
const [open, setOpen] = useState(false);
return (
Sheet Content
);
}
```
### Sheet with Top Side
This example demonstrates a sheet that slides in from the top.
```tsx
import React, { useState } from "react";
import { Button, Sheet } from "@firecms/ui";
export default function SheetTopDemo() {
const [open, setOpen] = useState(false);
return (
Sheet Content
);
}
```
### Transparent Sheet
This sheet is configured to be transparent.
```tsx
import React, { useState } from "react";
import { Sheet } from "@firecms/ui";
export default function SheetTransparentDemo() {
const [open, setOpen] = useState(false);
return (
Transparent Sheet Content
);
}
```
## Skeleton
The `Skeleton` component is used as a placeholder while content is loading. It provides a simple visual representation of the component that is being loaded, typically as a gray or light-colored block.
### Usage
To use the `Skeleton`, import it from your components and pass the `width`, `height`, and `className` props to customize its appearance.
### Basic Skeleton
A simple skeleton with default width and height.
```tsx
import React from "react";
import { Skeleton } from "@firecms/ui";
export default function SkeletonBasicDemo() {
return ;
}
```
### Custom Sized Skeleton
A skeleton component that showcases custom width and height.
```tsx
import React from "react";
import { Skeleton } from "@firecms/ui";
export default function SkeletonCustomSizeDemo() {
return (
<>
>
);
}
```
### Skeleton With Custom Classes
Demonstrates usage of the `className` prop to apply custom styles.
```tsx
import React from "react";
import { Skeleton } from "@firecms/ui";
export default function SkeletonCustomClassDemo() {
return ;
}
```
## Slider
Sliders allow users to input a value by sliding a handle along a track. This component is highly customizable with various options for orientation, step, range, and more.
### Usage
To use the `Slider`, import it from your components library and configure it using props such as `min`, `max`, `step`, `value`, and others.
### Basic Slider
A basic example of the Slider component with default settings.
```tsx
import React, { useState } from "react";
import { Slider } from "@firecms/ui";
export default function SliderBasicDemo() {
const [value, setValue] = useState([60]);
return (
);
}
```
### Range Slider
An example of a range slider with two handles that allow users to select a range of values.
```tsx
import React, { useState } from "react";
import { Slider } from "@firecms/ui";
export default function SliderRangeDemo() {
const [value, setValue] = useState([50, 70]);
return (
);
}
```
### Small Slider
A smaller version of the Slider component with a reduced size.
```tsx
import React, { useState } from "react";
import { Slider } from "@firecms/ui";
export default function SliderSmallDemo() {
const [value, setValue] = useState([50]);
return (
);
}
```
### Disabled Slider
Illustrating how to use the `disabled` prop to create a non-interactive Slider.
```tsx
import React from "react";
import { Slider } from "@firecms/ui";
export default function SliderDisabledDemo() {
return (
);
}
```
### Inverted Slider
An example of an inverted slider where the value decreases from left to right.
```tsx
import React, { useState } from "react";
import { Slider } from "@firecms/ui";
export default function SliderInvertedDemo() {
const [value, setValue] = useState([70]);
return (
);
}
```
## Table
The `Table` component is a flexible data container that allows you to display tabular data with various customization options. The table is composed of several subcomponents including `TableBody`, `TableHeader`, `TableRow`, and `TableCell` which offer distinct styling for different sections of the table.
### Usage
To use the `Table` component, you will generally use a combination of `Table`, `TableBody`, `TableHeader`, `TableRow`, and `TableCell` components.
### Basic Table
A basic table showcasing the default structure.
```tsx
import React from "react";
import { Table, TableBody, TableHeader, TableRow, TableCell } from "@firecms/ui";
export default function TableBasicDemo() {
return (
Name
Age
City
John Doe
30
New York
Jane Smith
25
San Francisco
);
}
```
### Table with Custom Styling
Apply any style or base attributes to the table components.
```tsx
import React from "react";
import { Table, TableBody, TableHeader, TableRow, TableCell } from "@firecms/ui";
export default function TableCustomHeadingDemo() {
return (
Product
Price
Stock
console.log("Clicked")}>
Apple
$1.00
In Stock
console.log("Clicked")}>
Banana
$0.50
Out of Stock
);
}
```
## Tabs
Tabs are used for navigation between different views or sections within the same context.
### Usage
To use the `Tabs` component, import it from your components along with the child `Tab` components and pass the required props.
### Basic Tabs
A simple tab example with minimal configuration.
```tsx
import React, { useState } from "react";
import { Tabs, Tab } from "@firecms/ui";
export default function TabsBasicDemo() {
const [value, setValue] = useState("tab1");
return (
Tab 1
Tab 2
Tab 3
);
}
```
## Text Field
## Text Field Component
Text fields are versatile UI elements that allow users to input, edit, and display text within an application.
They play a crucial role in user interaction, providing a straightforward way for users to enter data, provide
feedback, complete forms, and interact with various interfaces that require text input. Text fields can be used for
short inputs like usernames or passwords, as well as for longer text entries like comments, messages, or
detailed descriptions.
In the context of FireCMS UI, the design principles and components are loosely based on Google's Material Design guidelines. This means that
the text fields in FireCMS benefit from a consistent and user-friendly design language, ensuring a cohesive
look and feel across different web applications. By leveraging these components, developers can quickly build
interactive and visually appealing forms and input areas that enhance user experience and maintain design consistency.
### Usage
To use the `TextField`, import it from your components directory and pass the `value`, `onChange`, and other necessary props to fit your use case.
### Basic Text Field
A basic text field with minimal configuration:
```tsx
import React, { useState } from "react";
import { TextField } from "@firecms/ui";
export default function TextFieldBasicDemo() {
const [value, setValue] = useState("");
return (
setValue(e.target.value)}
label="Basic Text Field"
placeholder="Enter text"
/>
);
}
```
### Multiline Text Field
You can create a multiline text field by setting the `multiline` prop to `true`. This is useful for longer text inputs like comments or descriptions.
```tsx
import React, { useState } from "react";
import { TextField } from "@firecms/ui";
export default function TextFieldMultilineDemo() {
const [value, setValue] = useState("");
return (
setValue(e.target.value)}
label="Multiline Text Field"
placeholder="Enter text"
multiline
minRows={4}
/>
);
}
```
### Sizes
The `TextField` component comes in various sizes to fit different layout needs. You can adjust the size using the `size` prop.
```tsx
import React, { useState } from "react";
import { TextField } from "@firecms/ui";
export default function TextFieldSizeDemo() {
const [value, setValue] = useState("");
return (
setValue(e.target.value)}
label="Small Size"
placeholder="Small size"
size="small"
/>
setValue(e.target.value)}
label="Medium Size"
placeholder="Medium size"
size="medium"
/>
setValue(e.target.value)}
label="Large Size"
placeholder="Large size"
size="large"
/>
);
}
```
### Adornments
You can add adornments to the beginning or end of a text field to provide additional context or functionality.
```tsx
import React, { useState } from "react";
import { TextField } from "@firecms/ui";
export default function TextFieldAdornmentDemo() {
const [value, setValue] = useState("");
return (
setValue(e.target.value)}
label="Text Field with Adornment"
placeholder="Enter text"
endAdornment={@}
/>
);
}
```
## TextareaAutosize
The `TextareaAutosize` component automatically adjusts its height to fit the content.
### Usage
To use the `TextareaAutosize` component, import it from your components and pass the necessary props.
### Basic TextareaAutosize
A simple `TextareaAutosize` with basic usage.
```tsx
import React from 'react';
import { TextareaAutosize } from '@firecms/ui';
export default function TextareaAutosizeBasicDemo() {
return (
);
}
```
### Controlled TextareaAutosize
An example of a controlled `TextareaAutosize` component.
```tsx
import React, { useState } from "react";
import { TextareaAutosize } from "@firecms/ui";
export default function TextareaAutosizeControlledDemo() {
const [value, setValue] = useState("Controlled textarea");
const handleChange = (event: React.ChangeEvent) => {
setValue(event.target.value);
};
return (
);
}
```
### TextareaAutosize with Max and Min Rows
Demonstrating how to set the minimum and maximum number of rows.
```tsx
import React from "react";
import { TextareaAutosize } from "@firecms/ui";
export default function TextareaAutosizeRowsDemo() {
return (
);
}
```
## What's new in FireCMS 3.0
The new version of FireCMS is a major release that brings a lot of new features
and improvements. This page will guide you through the most important changes 🔥
FireCMS 3.0 comes in three flavors: **FireCMS Cloud**, **FireCMS Community** and **FireCMS PRO**.
### FireCMS Cloud
FireCMS Cloud makes now use of a **backend** to provide a more flexible and
customizable experience. This backend is hosted by us, and you don't have to
worry about it. You can still use FireCMS Cloud for free, but you will have to
pay for some features.
Until now, FireCMS was a frontend-only library that would connect directly to
the client-side Firebase SDK. This was great for simple use cases, but it
limited the flexibility of the library. For example, it was not possible to
customize collections from the UI, since they were hard coded in the clients
code.
Having a backend allows us to store configuration in Firestore, and to make it easy for end users
to modify the schemas. We have done a lot of work to **prevent polluting your database** with
FireCMS' configuration.
Having a backend also allows us to provide a better experience by offering additional features
such as data enhancement through OpenAI and Google's latest models.
### FireCMS PRO
FireCMS PRO is a version of FireCMS that you can host yourself. This means that
you have full control over your data, and you can customize the CMS to your
needs. You can try out FireCMS PRO for free, but you will need a license to use.
FireCMS PRO is great for large projects, or when you need to customize the CMS
to your needs. You can achieve the highest level of customization of all previous FireCMS versions.
FireCMS PRO is perfect for agencies that are looking to provide a CMS to their clients.
If you are an agency, feel free to [reach out to us](https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ0INW8ihjQ90S4gkdo8_rbL_Zx7gagZShLIpHyW43zDXkQDPole6a1coo1sT2O6Gl05X8lxFDlp?gv=true)
to get a demo of FireCMS PRO.
### FireCMS Community
FireCMS Community is a free and open source version of FireCMS that you can
host yourself. This means that you have full control over your data, and you can
customize the CMS to your needs.
MIT licensed and completely free, FireCMS Community is a great option for
small projects that just need a CMS. It does not include all the bells and whistles
of FireCMS Cloud or FireCMS PRO, but it is still a powerful alternative, due to
its customizability and flexibility.
This version has all the functionality of FireCMS 2.0 but with many of the improvements
of the latest version, so it is great if you are looking to update and benefit from the
UI updates and performance improvements.
### New UI collection schema editor
Until now, the collection schema was defined in the client-side code. This was
great for simple use cases, but it limited the flexibility of the library. For
example, it was not possible to customize collections from the UI, since they
were hard coded in the clients code.
In FireCMS Cloud, **the collection schema is stored in FireCMS backend**, but you are also able to define
your collections in code for greater flexibility. Your end users will be able to modify the
collection schema. Let's say you have a collection of `Posts` and you want to
add a new possible value for the enum `status`. You can now open the collection
editor and add the new value. Even better, FireCMS can find new values and add
them to your schema with one click!
You can still limit the properties that can be modified from the UI, and you
can also define the default values for new documents.
#### New data inference
Do you have a few collections in your project, and you want to get started
quickly? FireCMS can now **infer the schema from your data**. This means that
you can get started with FireCMS in a few minutes, without having to write a
single line of code.
### Local text-based search
In previous versions of FireCMS, you could add your external search engine solution
like Algolia or ElasticSearch. This is still possible, but now you can also
**search your data locally**. This means that you can search your data without
having to pay for an external service. This is great for small projects, or
when you are just getting started.
This feature is meant to be used with **small datasets**. If you have a large
dataset, you should still use an external search engine.
### Data import and export
#### Export
You now have better control of how your data is **exported**:
- Define the format of your dates
- Define how arrays get serialized in CSV files (assign one column per array item, or
serialize the array as a string)
- Export your data as JSON or CSV.
#### Import
You can now **import data** from CSV, JSON or Excel files. This is great for migrating data
from other systems. We incorporate a modern UI that allows to define how the data is imported
and how it is mapped to your collections.
### Tailwind migration and performance improvements
Versions 1.0 and 2.0 of FireCMS were based on Material UI (mui). This was great
for getting started quickly, but it had some drawbacks. The main one was that
**performance was not great**. The styling solution of MUI is based on emotion
which resolves styles at runtime. This means that the browser has to do a lot of
work to resolve the styles. This is not a problem for small applications, but
it can be a problem for large applications.
In FireCMS 3.0 we have migrated to Tailwind CSS. This is a utility-first CSS
framework that allows us to generate a small CSS file with all the styles
resolved at build time. This means that the browser does not have to do any
work to resolve the styles, which results in a **much faster experience**. 🚀
### New authentication system
We now provide a new authentication system that allows managing CMS users
and roles from the UI.
If you prefer defining permissions for each collection in code, you can still do it.
### New component library and Icons
We have also created a new component library that you can use to build your own components
(or use it in any project you like really!). You can get it from npm:
```bash
npm install @firecms/ui
```
or
```bash
yarn add @firecms/ui
```
(you only need to install it if you want to use it in a different project)
The components are fully typed and documented. You can find the documentation [here](/docs/components).
They are based on Tailwind CSS and Radix UI. They are easily customizable, and you can
use them to build your own components. They are also accessible and responsive.
### Icons collection
You can customise the icons in different parts of the CMS, such when selecting one for
a collection. FireCMS UI now exports all the Material Icons, conveniently exported as React components.
We have also added an [icon search function](/docs/icons) in the website to find the icon you need.
It makes it easy to find the right icon key when defining configuration for your collections, or to
find the right icon component when you want to use it in your own components.
### New CLI
Use the CLI to deploy your custom code with a single command:
```bash
firecms deploy
```