# FireCMS Documentation > This is the documentation for FireCMS, a headless CMS for Firebase/MongoDB. It is a powerful tool to manage your data, > with a focus on developer experience and extensibility. > Easy to get started, easy to customize and easy to extend.FireCMS is great both for existing projects, since it will > adapt to any database structure you have, as well as for new ones. ## Getting started :::important FireCMS is an **open source headless CMS and admin panel**. It is a platform where you can build full companies, or your weekend side project. ::: FireCMS uses [**Firebase**](https://firebase.google.com/) or **MongoDB Atlas** as a **backend**. You are the **owner** of your Firebase project, and FireCMS is a tool that helps you build your admin panel on top of it. FireCMS provides all the editing options you lack in a simple Firebase project. FireCMS creates **CRUD views** based on your configurations with ease. It's simple to set up for common cases and just as easy to extend and customize to fit your specific needs. FireCMS imposes **no data structure restrictions**, allowing seamless integration with any project right from the start. FireCMS **3.0** is the latest version of FireCMS. It can be used in different ways: - As a managed service in the Cloud: [**FireCMS Cloud**](https://app.firecms.co). In this version you can create and manage your content in a user-friendly interface, and use it as a no-code tool, or extend its functionality with code. - You also have self-hosted options in the [**PRO**](/docs/pro) plan. In this version you need to deploy FireCMS to your server, and you have full control over the code, with many customization options. #### Navigation FireCMS takes care of the **navigation** for you, it generates routes and menus based on the configuration that you set up. :::tip The collections can be defined asynchronously, so you can fetch data from your backend to build them. It might be useful if you want to build the collections based on the logged-in user, or if you want to fetch some data to build the schema of your collections. Check the [**dynamic collections**](https://firecms.co/docs/collections/dynamic_collections) section for more information. ::: You have two main ways of creating the top-level views in FireCMS, either creating **entity collections** that get mapped to CMS views, or create your own top-level **React views**: - Check all the possible configurations for defining [**collections**](https://firecms.co/docs/collections/collections) - Otherwise, you can define your own [**custom top-level views**](https://firecms.co/docs/custom_top_level_views.mdx). ## ✨ 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 two flavors: **FireCMS Cloud** 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 OpenAPI GPT-4. ### 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. ### 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 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 ``` ## Cloud Quickstart :::note Start here if you want to customize FireCMS Cloud. You can run a FireCMS project without writing a single line of code from [FireCMS Cloud](https://app.firecms.co). This guide is for developers who want to add custom code to their FireCMS project. ::: In order to add custom code to your FireCMS project, you need: - A **Firebase** project - A **FireCMS** project You can create both projects from FireCMS Cloud. Once you have both projects created you can initialize your codebase by running: ``` npx create-firecms-app ``` or ``` yarn create firecms-app ``` Make sure to select the option FireCMS Cloud, and follow the instructions in the CLI. This will create a new folder with all the code you need to get started. In the code you will be able to add custom collections, custom pages, custom fields, custom actions, custom properties, etc. ### Running your project To run your project locally, you can run the following command, like any other Vite project: ``` npm run dev ``` or ``` yarn dev ``` This will execute a version of your project that uses FireCMS backend to store config data but runs locally. You should be able to see your FireCMS instance in your browser, including all the configuration you have already created in the Cloud version... Awesome! If you want to deploy to FireCMS Cloud your module must export a `FireCMSAppConfig` object. You can find more information about this object in the [App config section](https://firecms.co/docs/cloud/app_config) reference. :::important Vite uses the default url `http://127.0.0.1:5173` for the development server in versions of `node` < 18.0.0. If you are using a version of node < 18.0.0, you will need to add this url to the authorized domains in the Firebase console. Firebase Auth will require to add this url to the authorized domains in the Firebase console. Alternatively, you can use the url `http://localhost:5173`. ::: ### How does code upload work? We use module federation to upload your code to FireCMS Cloud. This means that you can upload your code to FireCMS Cloud without having to worry about the infrastructure. When you run the command `yarn deploy` or `npm run deploy`, your code will be uploaded to FireCMS Cloud and will be available in the FireCMS Cloud instance. Only your code will be uploaded, and it will be integrated into the core FireCMS Cloud. This means that you can use the same authentication system, the same collections, the same pages, etc. This approach allows you to have a CMS that is fully customizable while still benefiting of the continuous improvements and updates that we provide in FireCMS Cloud. ## App Config The app config is the main configuration object of FireCMS Cloud. It is defined by the interface `FireCMSAppConfig`. In order to customize the CMS, you need to create a project in [app.firecms.co](https://app.firecms.co) and initialise a new project in code with `npx create-firecms-app` or `yarn create firecms-app`. After those steps are ready you can use the `FireCMSAppConfig` export to add your own customizations, like custom reusable properties or fields, collections, entity views, cms views, etc. #### Customization options Let's see all the customization options available: * `collections`: 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. See [Collections](https://firecms.co/docs/collections) for more information. * `modifyCollection`: Function to modify a collection before it is added to the CMS. If you are defining collections in the UI, you can use this function to modify them. This callback is called for each collection. * `views`: Custom additional views created by the developer, added to the main navigation. See [Custom top level views](https://firecms.co/docs/top_level_views) for more information. * `propertyConfigs`: List of custom property configs 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. See [Property configs](https://firecms.co/docs/properties/reusing_properties) for more information. * `entityViews`: 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. See [Entity views](https://firecms.co/docs/collections/entity_views) for more information. * `HomePage`: Use this component to override the home page. * `fireCMSAppBarComponentProps`: Additional props passed to the custom AppBar. * `firestoreIndexesBuilder`: Use this builder to indicate which indexes are available in your Firestore database. This is used to allow filtering and sorting for multiple fields in the CMS. * `onFirebaseInit`: Optional callback after Firebase has been initialised. Useful for using the local emulator or retrieving the used configuration. * `dateTimeFormat`: Format of the dates in the CMS. Defaults to 'MMMM dd, yyyy, HH:mm:ss'. * `locale`: Locale of the CMS, currently only affecting dates. * `textSearchController`: Use this controller to return text search results as document ids, that get then fetched from Firestore. * `autoOpenDrawer`: Experimental feature to open the drawer automatically when hovering. * `appCheck`: Firebase App Check configuration. - `provider`: The provider to use for App Check. - `isTokenAutoRefreshEnabled`: Whether to automatically refresh the token. - `debugToken`: Debug token to use for App Check. - `forceRefresh`: Whether to force refresh the token. #### Example An app config is generated for you automatically when you initialise a new project, but here is an example of how you can customize it: ```tsx const appConfig: FireCMSAppConfig = { version: "1", // Collections are the main navigation entries in the CMS collections: async ({ authController, dataSource }) => { // Sample of fetching some data from the database in order to build dynamic collections const firstProducts = await dataSource.fetchCollection({ path: "products", limit: 5 }); return ([ // ...your collections ]); }, // Top level views, not bound to any collection views: [ { path: "sales_report", name: "Sales report", icon: "extension", view: }, ], // Modify collection allows you to modify collections before they are added to the CMS modifyCollection: ({ collection }) => { if (collection.id === "products") { return { ...collection, name: "Products modified", entityActions: [ { name: "My custom action", onClick: ({ entity }) => { console.log("Entity", entity); } } ] } } return collection; }, // properties that can be reused in different collections propertyConfigs: [ { name: "Translated string", key: "translated_string", property: { dataType: "map", properties: { en: { dataType: "string", name: "English" }, es: { dataType: "string", name: "Español" }, }, }, } ], // views that can be assigned to entities entityViews: [{ key: "test", name: "Test", Builder: SampleEntityView }] } export default appConfig; ``` ## Deployment ### Deployment to FireCMS Cloud FireCMS is unique among CMSs in that it allows to upload custom code to its Cloud version. This is a very advanced feature enables you to tailor the CMS according to your requirements. The code is bundled and compiled using **module federation** and **vite**. This means that you can use any npm package you want to build your CMS. The bundle will not include any of the dependencies that are already included in FireCMS, so you can use any version of any package you want. Deploy your code to [FireCMS Cloud](https://app.firecms.co) with a single command, and it will be served from there: ```bash yarn deploy ``` The benefit of this approach is that you can use any npm package you want, and you can use the latest version of FireCMS without having to manually update your code. #### FireCMS CLI The FireCMS CLI is a tool that allows you to deploy your CMS to FireCMS Cloud with a single command. In your project, you should have `firecms` as a dev dependency. This package was previously `@firecms/cli`. The available commands are: ```bash firecms login ``` ```bash firecms logout ``` and ```bash firecms deploy --project=your-project-id ``` ### Deployment FireCMS Cloud projects can only be deployed to FireCMS Cloud. If you need a self-hosted version of FireCMS, you can use the PRO plan, or use the community version. Since the APIs are the same for all versions, you can easily switch between them. ## 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 `app.firecms.co` to the list of allowed domains in AppCheck provider configuration. ::: You can enable Firebase App Check in your app directly in the **project settings** in the FireCMS Cloud console, or by providing the `appCheck` prop in your app configuration. By implementing it in code, you can have more control over the configuration and the provider you want to use, including custom providers. You can define some options: - `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. ### 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 appConfig: FireCMSAppConfig = { version: "1", appCheck: { provider: new ReCaptchaEnterpriseProvider("your-site-key") }, } export default appConfig; ``` #### 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 appConfig: FireCMSAppConfig = { version: "1", appCheck: { provider: new ReCaptchaV3Provider("your-site-key") }, } export default appConfig; ``` #### Custom provider You can also create a custom provider by implementing the `AppCheckProvider` interface. ### Check your configuration FireCMS Cloud can share dependencies with your uploaded app. It is important than your app and FireCMS Cloud use the same version of Firebase App Check. In order to do so, make sure, in your `vite.config.ts`, you are using the shared dependency provided by FireCMS. You need to have the dependency `@firebase/app-check` in your `vite.config.ts`. Federation plugin `shared` configuration. This is a sample configuration: ```tsx // 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" // Add this line ] }) ], build: { modulePreload: false, minify: false, target: "ESNEXT", cssCodeSplit: false, } }); ``` ## Creating a project from a service account :::important You **only** need to create a service account manually for FireCMS Cloud projects, in case you don't want to grant FireCMS admin permissions. In the app, you can create new or link to existing projects without the need to create a service account manually. ::: One possible way to create a FireCMS Cloud project and link it to your **existing** Firebase/GCP project is by creating a service account, assigning the necessary permissions, and linking it to your project. In order to do so please follow these steps: - Go to the [Google Cloud Console](https://console.cloud.google.com/). - Select the project you want to link to FireCMS. - Go to the [Service accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) section. {"Service - Click on the `Create Service Account` button. {"Create - Fill in the details for the service account. Name it `FireCMS`. {"Service - Assign the following roles: - `Firebase Admin` - `Firebase Admin SDK Administrator Service Agent` - `Firebase Service Management Service Agent` {"Service - Optionally, define the users that can impersonate the service account. {"Service - 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`. {"Service - Then create a new key. {"Create - And finally, download the JSON key. {"Download 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-canary", "firebase": "^10.12.2", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@originjs/vite-plugin-federation": "^1.3.5", "@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. ``` yarn add @mui/material @emotion/react @emotion/styled ``` If you need MUI icons, run: ``` 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` - `CircularProgress` size is a string instead of a number. You can use `size="small"` or `size="large"`. #### Deployment FireCMS 3.0 is now deployed in our own service, and reachable through [app.firecms.co](https://app.firecms.co). You can still deploy it in your own Firebase project. The same build you generate for running the CMS locally can be deployed to Firebase hosting, or any other hosting service. ## Eject Collections The **newest version of [FireCMS](https://app.firecms.co)**, version 3.0 allows you to fully manage the CMS in the Cloud without having to manage code, so you can create collections using our **UI to create any schema** you need. Consider the Books collection from our [demo](https://demo.firecms.co). The collection includes fields such as Title, Subtitle, Authors, Rating, Category, etc. You can create this collection using the UI, and then manage it from the UI. Any text, dates, images, list of text or other fields that you may think of. ![edit_collection_in_ui](/img/docs/collections/eject_collections_book_collection.png) But what if you need to **customize** the view **further**, or prefer to use code to create the collection? FireCMS allows you to do just that. You can start a customization process with our [quickstart guide](https://firecms.co/docs/cloud/quickstart) and then upload it to the Cloud, using the same hosted app. A common question we receive is how to import the code of a collection. It's as simple as navigating to the Schema Editor, clicking the Code button, and copying the generated code. You can then **eject the collection** and manage it directly from your codebase. ![edit_collection_code_button](/img/docs/collections/eject_collections_code_button.png) Next, paste the copied code into your preferred Code Editor. ![edit_collection_code_button](/img/docs/collections/eject_collections_code.png) :::important This feature is available across all plans, including free and plus subscriptions. We believe in the power of code and want to make it accessible to everyone. ::: In summary, the latest version of [FireCMS](https://app.firecms.co) provides a **seamless No Code experience** for managing your CMS. However, if you need to customize your CMS further, you can easily switch to a Code-based approach. All within a single app, [app.firecms.co](https://www.notion.so/e8af421701bb440ebcc4ef4ce265e9ad?pvs=21). Here's the full code generated by the UI for the Books collection: ```tsx const BooksCollection: EntityCollection = { id: 'books', name: 'Books', path: 'books', editable: true, icon: 'table_rows', group: '', properties: { title: { name: 'Title', validation: { required: true, }, dataType: 'string', }, subtitle: { dataType: 'string', name: 'Subtitle', }, authors: { name: 'Authors', dataType: 'string', }, average_rating: { dataType: 'number', name: 'Average Rating', }, category: { name: 'Category', dataType: 'string', enumValues: [ { id: 'drama', label: 'Drama', }, { label: 'Education', id: 'education', }, { label: 'Fantasy', id: 'fantasy', }, { id: 'fantasy-fiction', label: 'Fantasy Fiction', }, { label: 'Fiction', id: 'fiction', }, { id: 'history', label: 'History', }, { id: 'juvenile-fiction', label: 'Juvenile Fiction', }, { id: 'philosophy', label: 'Philosophy', }, { id: 'religion', label: 'Religion', }, { label: 'Science', id: 'science', }, { id: 'self-help', label: 'Self Help', }, { id: 'travel', label: 'Travel', }, ], }, created_at: { name: 'Created At', dataType: 'date', }, description: { name: 'Description', dataType: 'string', }, isbn10: { name: 'Isbn10', dataType: 'number', }, isbn13: { name: 'Isbn13', dataType: 'number', }, num_pages: { name: 'Num Pages', dataType: 'number', }, published_year: { dataType: 'number', name: 'Published Year', }, ratings_count: { name: 'Ratings Count', dataType: 'number', }, spanish_description: { name: 'Spanish Description', dataType: 'string', }, tags: { dataType: 'array', of: { dataType: 'string', editable: true, name: 'Tags', }, name: 'Tags', }, thumbnail: { dataType: 'string', url: true, name: 'Thumbnail', }, }, subcollections: [], } ``` ## Quickstart FireCMS Community and PRO are the self-hosted versions of FireCMS. FireCMS Community provides all the functionality that has made FireCMS a reference in the CMS space. FireCMS PRO includes all the most advanced features added in version 3.0, including: - Collection Schema Editor UI - Data export and import - LLM autocompletion. - Advanced Notion-style text editor - User and roles management It is designed for projects that require more control over the infrastructure, data and user management. It is a great option for companies that need to comply with specific regulations, or that have specific requirements that are not covered by the cloud or community versions. FireCMS is a fully open-source project, and it is built on top of Firebase, Firestore, and Firebase Authentication. It can be also used with MongoDB Atlas, or any other backend by implementing the required interfaces. It is designed to be deployed on your own infrastructure, and it is fully customizable. It allows you a high level of customization, and it is designed to be extended with your own components, authentication providers, custom views, dashboard, custom logic, you name it. #### Create a new project using the CLI To create a new project using the CLI, you can run the following command: ```bash npx create-firecms-app ``` or: ```bash yarn create firecms-app ``` This will create a new FireCMS, Community or PRO, project in the selected folder. A FireCMS project is a React application that you can customize to fit your needs. The initial project will have a basic structure with a few collections and a couple of custom views. #### Running the project To run the project, you run the following commands: ```bash cd my-cms yarn yarn dev ``` This will install the dependencies and start the development server. ## Quickstart + Next.JS frontend :::tip Get a fully fledged CMS with an e-commerce/blog template ready to be adapted to your logic ::: ![next_js_frontend.png](/img/next_js_frontend.png) You can check a **demo** of this template: - [**Admin panel**](https://next.firecms.co/cms) - [**Frontend**](https://next.firecms.co/products) You can change the data in the demo and see the updates, but it gets reset every **hour**. Get a **frontend template** with sample **CRUD views** that include: - Integration with **Firebase** and **FireCMS**. Reuse components both in frontend and admin panel. - **Live preview**: see how the changes in the CMS will be reflected in the website, using the exact same code. - UI implemented with **tailwindcss** and **Radix UI components**. - Advanced **filtering options** - Data fetch on **scrolling**. - Storing filter state in **URL**. This template is extremely easy to **customize** to your needs. ### Using the **FireCMS PRO starter template** The easiest way to use FireCMS with Next.js is to use the **FireCMS PRO starter template**. This template includes a Next.js project with FireCMS already configured. You can create a new project using the **FireCMS PRO template** by running: ```bash npx create-firecms-app ``` or ```bash yarn create firecms-app ``` and select the `**FireCMS PRO with Next.js frontend**` template. Then follow the instructions on the screen to create your project. #### What you get The code that will be generated for you is a Next.JS project split in **3 parts**: - A **FireCMS instance** to manage your data. - A **frontend app** that implements CRUD functionality for a products collection, as well as a blog view. - A **common folder** with shared components. ### Setting up **FireCMS** with **Next.js** manually You can use FireCMS with Next.js. FireCMS is a **React library**, so you can use it with any React framework. In the case of Next.js, you are restricted to running FireCMS on the **client side**, as Next.js does not support server side rendering of some of the React components used by FireCMS. Let's build an app using FireCMS and Next.js, with the app router configured to delegate all the routes starting with `/cms` to **FireCMS**. #### Create a **Next.js project** Start by creating your Next.js project: ```bash npx create-next-app@latest ``` Select: - **TypeScript** as the language - **ESLint** as the linter - **Tailwind CSS** as the CSS framework - **src** as the root directory - Yes to the **app router** prompt - Yes to customize the **default import alias** (optional) #### Install **FireCMS** Then we are going to install **FireCMS PRO**. Note that we will not be adding all the plugins like the collection editor or data enhancement, but you can add them as needed. Then install FireCMS and its dependencies: ```bash yarn add firebase@^10 @firecms/core@^3.0.0-beta @firecms/firebase@^3.0.0-beta @firecms/editor@^3.0.0-beta react-router@^6 react-router-dom@^6 @tailwindcss/typography typeface-rubik @fontsource/jetbrains-mono ``` Now let's import the tailwind config of FireCMS. Add the **FireCMS preset** `tailwind.config.js`, as well as the content paths to FireCMS source code, so the right tailwind classes are picked. ```js const config: Config = { presets: [fireCMSConfig], content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}", "./src/cms/**/*.{js,ts,jsx,tsx,mdx}", "./node_modules/@firecms/**/*.{js,ts,jsx,tsx}" ] }; export default config; ``` #### Disable **yarn pnp** (optional) We prefer disabling yarn pnp for this project. You can do this by creating the file `.yarnrc` in the root of your project with the following content: ```bash nodeLinker: node-modules ``` #### Configuring the **App router** Next.js uses a file-based router. In this guide, we will be creating the FireCMS app in the `/cms` route, but you can customize this to your needs. FireCMS uses **react-router**, so we need to configure Next.js to delegate all the routes starting with `/cms` to **FireCMS**. In our `app` folder, we create a folder called `cms` and inside it another one called `[[...path]]`. This will match any route starting with `/cms`. Then create the file `cms/[[...path]]/page.tsx` with the following content: :::important If you are not running FireCMS in the root path of your app, you need to set the `basePath` prop to the path where you are running it. In this case, we are running it in `/cms`. ::: ```tsx "use client"; export default function CMS() { return ; } ``` #### Creating the **CMS** Now let's create the FireCMS components. Create the file `./src/cms/FireCMSApp.tsx` with the following content. Remember to replace the `firebaseConfig` with your own Firebase configuration. ```tsx "use client"; import React, { useCallback } from "react"; import "./index.css"; import "typeface-rubik"; import "@fontsource/jetbrains-mono"; import { AppBar, buildCollection, CircularProgressCenter, Drawer, FireCMS, ModeControllerProvider, NavigationRoutes, Scaffold, SideDialogs, SnackbarProvider, useBuildLocalConfigurationPersistence, useBuildModeController, useBuildNavigationController, useValidateAuthenticator } from "@firecms/core"; import { FirebaseAuthController, FirebaseLoginView, FirebaseSignInProvider, useFirebaseAuthController, useFirebaseStorageSource, useFirestoreDelegate, useInitialiseFirebase, } from "@firecms/firebase"; import { useImportPlugin } from "@firecms/data_import"; import { useExportPlugin } from "@firecms/data_export"; import { useBuildUserManagement, userManagementAdminViews, useUserManagementPlugin } from "@firecms/user_management"; import { useFirestoreCollectionsConfigController } from "@firecms/collection_editor_firebase"; import { mergeCollections, useCollectionEditorPlugin } from "@firecms/collection_editor"; //TODO: replace with your own Firebase config export const firebaseConfig = { //... }; const categories = { fiction: "Fiction", drama: "Drama", "fantasy-fiction": "Fantasy fiction", history: "History", religion: "Religion", "self-help": "Self-Help", "comics-graphic-novels": "Comics & Graphic Novels", "juvenile-fiction": "Juvenile Fiction", philosophy: "Philosophy", fantasy: "Fantasy", education: "Education", science: "Science", medical: "Medical", cooking: "Cooking", travel: "Travel" }; const booksCollection = buildCollection({ name: "Books", singularName: "Book", id: "books", path: "books", icon: "MenuBook", group: "Content", textSearchEnabled: true, description: "Example of a books collection that allows data enhancement through the use of the **OpenAI plugin**", properties: { title: { name: "Title", validation: { required: true }, dataType: "string" }, authors: { name: "Authors", dataType: "string" }, description: { name: "Description", dataType: "string", multiline: true }, spanish_description: { name: "Spanish description", dataType: "string", multiline: true }, thumbnail: { name: "Thumbnail", dataType: "string", url: "image" }, category: { name: "Category", dataType: "string", enumValues: categories }, tags: { name: "Tags", dataType: "array", of: { dataType: "string" } }, published_year: { name: "Published Year", dataType: "number", validation: { integer: true, min: 0 } }, num_pages: { name: "Num pages", dataType: "number" }, created_at: { name: "Created at", dataType: "date", autoValue: "on_create" } } }); export function FireCMSApp() { const { firebaseApp, firebaseConfigLoading, configError } = useInitialiseFirebase({ firebaseConfig }); // Controller used to manage the dark or light color mode const modeController = useBuildModeController(); const signInOptions: FirebaseSignInProvider[] = ["google.com"]; // Controller for saving some user preferences locally. const userConfigPersistence = useBuildLocalConfigurationPersistence(); // 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 }); const collectionConfigController = useFirestoreCollectionsConfigController({ firebaseApp }); // 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 }); const { authLoading, canAccessMainView, notAllowedError } = useValidateAuthenticator({ disabled: userManagement.loading, authenticator: userManagement.authenticator, authController, // authenticator: myAuthenticator, dataSourceDelegate: firestoreDelegate, storageSource }); const collectionsBuilder = useCallback(() => { const collections = [ booksCollection, // Your collections here ]; return mergeCollections(collections, collectionConfigController.collections ?? []); }, [collectionConfigController.collections]); const navigationController = useBuildNavigationController({ basePath: "/", collections: collectionsBuilder, collectionPermissions: userManagement.collectionPermissions, adminViews: userManagementAdminViews, authController, dataSourceDelegate: firestoreDelegate }); const userManagementPlugin = useUserManagementPlugin({ userManagement }); const importPlugin = useImportPlugin(); const exportPlugin = useExportPlugin(); const collectionEditorPlugin = useCollectionEditorPlugin({ collectionConfigController }); if (firebaseConfigLoading || !firebaseApp) { return <>; } if (configError) { return <>{configError}; } return ( {({ context, loading }) => { if (loading || authLoading) { return ; } if (!canAccessMainView) { return ; } return ; }} ); } ``` #### Import the default **FireCMS styles** Create a file called `index.css` in the `./src/cms` folder with the following content: ```css @import "@firecms/ui/index.css"; @tailwind base; @tailwind components; @tailwind utilities; :root { --fcms-primary: #0070F4; --fcms-primary-bg: #0061e610; --fcms-secondary: #FF5B79; } a { @apply text-blue-600 dark:text-blue-400 dark:hover:text-blue-600 hover:text-blue-800 } ``` #### Run Then simply run: ``` yarn dev ``` and navigate to `http://localhost:3000/cms` to see your **FireCMS** app running. #### Some considerations - Images are loaded differently in **Next.js**. You get a `StaticImageData` instead of the image URL (as in **vite**). You can use it in FireCMS components that expect a URL like, using the `src` property: ```tsx ``` ## Firestore Rules :::note These rules apply specifically to the FireCMS PRO plugins configuration. If you are using the community version you are encouraged to write your own rules to secure your data. ::: FireCMS PRO saves some configuration data in Firestore to manage user roles and permissions, as well as the collections configuration. In order to work properly, you need to set up the Firestore rules to allow the plugin to read and write to the specified paths. These are the default paths used by FireCMS (you can modify those paths in the specific plugin configuration): - `__FIRECMS/config/users` - `__FIRECMS/config/roles` - `__FIRECMS/config/collections` #### First time setup rules Depending on your project setup, the logged in user might not have permission to write to the Firestore database, in the FireCMS config path. In this case we suggest temporarily allowing access to the `__FIRECMS` path and subcollections. ``` match /__FIRECMS/{document=**} { allow read: if true; allow write: if true; } ``` #### Final suggested rules After you have created the first user and roles, you can restrict access to the `__FIRECMS` path again. We encourage you to set-up specific rules for your project, based on your security requirements. These are the rules that we suggest: ``` match /{document=**} { allow read: if isFireCMSUser(); allow write: if isFireCMSUser(); } function isFireCMSUser(){ return exists(/databases/$(database)/documents/__FIRECMS/config/users/$(request.auth.token.email)); } ``` These rules will allow users that have a CMS role to read and write all the data in your Firestore database. The roles will be enforced in the frontend by FireCMS, but if it is a requirement for your project, you can also enforce them in the Firestore rules, by setting your own custom rules. ## Sample PRO Let's go through the code generated by the FireCMS CLI after a PRO project is created. FireCMS is at its core a React library, so the generated code is a React application. The code is structured in a way that you can easily understand and modify it to fit your needs. #### Firebase Setup The first step involves initializing Firebase using the provided configuration. This is necessary for all Firebase-related operations throughout the app. You can find the `firebaseConfig` after creating a new webapp in the Firebase console. ```jsx const { firebaseApp, firebaseConfigLoading, configError } = useInitialiseFirebase({ firebaseConfig }); ``` This snippet sets up Firebase, checks for loading status, and handles configuration errors. #### Data Source and Storage Source Your users will need to interact with data and files, so you need to set up data and storage sources. FireCMS provides a Firestore delegate and Firebase storage source for these operations. ```jsx const firestoreDelegate = useFirestoreDelegate({ firebaseApp }); const storageSource = useFirebaseStorageSource({ firebaseApp }); ``` You are free to define your own data source and storage source, but these are the default ones provided by FireCMS. Feel free to reach out to us if you need help setting up your own data source or storage source. #### Collection Configuration Plugin The collection editor plugin allows you to include a UI for editing collection configurations. You can choose where the config is stored, and pass the configuration to the plugin. We include a controller that saves the configuration in your Firestore database. The default path is `__FIRECMS/config/collections`. The controller includes a few methods you can use in your own components to manage the collection configuration. ```jsx const collectionConfigController = useFirestoreCollectionsConfigController({ firebaseApp }); ``` You are free to define your collections in code, or use the UI to define them. You can also allow the modification in the UI of the collections defined in code. You can then merge the collections defined in code with the ones defined in the UI. ```jsx 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]); ``` In order to add the collection editor plugin, you need to include it in the list of plugins passed to the `FireCMS` component. ```jsx const collectionEditorPlugin = useCollectionEditorPlugin({ collectionConfigController }); ```` #### Authorization Management Managing user authentication and permissions is critical for security and proper access control. FireCMS provides an `Authenticator` interface that you can implement to define your own authentication logic. You can validate the user's access to the main view based on their authentication status and permissions. ```jsx const myAuthenticator: Authenticator = useCallback(async ({ user, authController }) => { if (user?.email?.includes("flanders")) { // You can throw an error to prevent access throw Error("Stupid Flanders!"); } const idTokenResult = await user?.firebaseUser?.getIdTokenResult(); const userIsAdmin = idTokenResult?.claims.admin || user?.email?.endsWith("@firecms.co"); console.log("Allowing access to", user); // we allow access to every user in this case return true; }, []); const { authLoading, canAccessMainView, notAllowedError } = useValidateAuthenticator({ authController, authenticator: myAuthenticator, dataSourceDelegate: firestoreDelegate, storageSource }); ``` (if you use the user management system, you can use the `authenticator` provided by the `UserManagement` controller. See below) ##### User Management FireCMS PRO includes a user management system that allows you to define roles and permissions for users. The `UserManagement` interface provides methods to define roles and permissions, as well as a loading state to manage. We include a controller that stores user roles and permissions in Firestore. You are free to define your own user management system. ```jsx const userManagement = useBuildUserManagement({ dataSourceDelegate: firestoreDelegate, }); ``` then build the user management plugin and include it in the list of plugins passed to the `FireCMS` component. ```jsx const userManagementPlugin = useUserManagementPlugin({ userManagement }); ``` You can delegate all the authentication logic to the user management system, by using the `authenticator` provided by the `UserManagement` controller. ```jsx const { authLoading, canAccessMainView, notAllowedError } = useValidateAuthenticator({ authController, disabled: userManagement.loading, authenticator: userManagement.authenticator, dataSourceDelegate: firestoreDelegate, storageSource }); ```` #### The Auth Controller The `AuthController` is the controller in charge of managing authentication. It provides methods to sign in, sign out, and get the current user. You can also define roles for users. You can access this controller from within your components using the `useAuthController` hook. ```jsx const authController: FirebaseAuthController = useFirebaseAuthController({ firebaseApp, signInOptions: ["google.com", "password"], // you can pick many more options loading: userManagement.loading, defineRolesFor: userManagement.defineRolesFor }); ``` In this case we are hooking the `AuthController` to the `UserManagement` controller, so we can define roles for users based on the user management system. #### Mode Controller & User Config Persistence Adjusting UI preferences, like theme mode, enhances user experience. ```jsx const modeController = useBuildModeController(); const userConfigPersistence = useBuildLocalConfigurationPersistence(); ``` These controllers enable theme mode toggling and local storage of user preferences. #### Navigation Controller The internal navigation controller manages the app's navigation, leveraging the collections and permissions setup. Here you can define your collections, views, and admin views. You can also pass the `authController` and `dataSourceDelegate` to the `NavigationController`. Optionally, you can define the collection permissions in the `UserManagement` controller. ```jsx 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]); // Here you define your custom top-level views const views: CMSView[] = useMemo(() => ([{ path: "example", name: "Example CMS view", view: }]), []); const navigationController = useBuildNavigationController({ collections: collectionsBuilder, views, authController, dataSourceDelegate: firestoreDelegate, adminViews: userManagementAdminViews, collectionPermissions: userManagement.collectionPermissions }); ``` This controller leverages the built collections and permissions setup to manage navigation efficiently. ### Wiring it all up Once you have all the controllers set up, you can pass them to the `FireCMS` component, along with the plugins you want to use. The `FireCMS` component will handle the rest, and render the main view or the login view based on the user's authentication status. Note how you can customize the main view based on the user's authentication status and permissions. The default login view is a Firebase login view, but you can define your own login view. The `SideDialogs` component is used to render the lateral dialogs, like the entity detail view. The `NavigationRoutes` component is used to render the main navigation routes. It uses `react-router-dom` to handle the routing, but you are free to replace it with your own routing system. ```jsx return ( {({ context, loading }) => { let component; if (loading || authLoading) { component = ; } else { if (!canAccessMainView) { component = ( ); } else { component = ( ); } } return component; }} ); ``` Find more details about the main components in the [Main Components](main_components) section. ## Main Components FireCMS provides a set of stylable components that scaffold the CMS interface. These components are designed to be easily customizable and can be extended to fit your needs. The main components are: #### Scaffold The `Scaffold` is typically the top level component for logged-in users. It provides the main layout for the CMS, including the drawer, the appbar, and the main content area. You can customize the `Scaffold` by providing your own components for the drawer, appbar, and content area. You can also apply classes to the `Scaffold` to style it according to your needs. ##### Props: - `autoOpenDrawer`: Open the drawer on hover. - `logo`: Logo to be displayed in the top bar and drawer. Note that this has no effect if you are using a custom AppBar or Drawer. - `className`: Additional classes to apply to the Scaffold. - `style`: Additional styles to apply to the Scaffold. - `children`: The children of the Scaffold. Typically, these are the AppBar, Drawer, NavigationRoutes, and SideDialogs. ##### Example: ```jsx //... return ``` #### AppBar The `AppBar` is the top bar of the CMS. It typically contains the logo, the title, and the user menu. The default appbar includes an avatar tied to the logged-user. ##### Props: - `title`: Title to be displayed in the appbar. - `endAdornment`: Component to be displayed on the right side of the appbar. - `startAdornment`: Component to be displayed on the left side of the appbar. - `dropDownActions`: Component to be displayed as a dropdown in the appbar. The content is displayed as children of a `Menu` component, so you will likely want to use `MenuItem` components. - `includeModeToggle`: Whether to include the color mode toggle in the appbar (dark/light mode). - `className`: Additional classes to apply to the AppBar. - `style`: Additional styles to apply to the AppBar. - `children`: Define your own AppBar content. If you define children, the title, endAdornment, and dropDownActions will be ignored. ##### Example: ```tsx //... return } dropDownActions={ <> { console.log("Settings clicked"); }}> Settings { console.log("Logout clicked"); }}> Logout }/> ``` ##### Replace the default AppBar You can replace the default AppBar by wrapping your custom component with the `AppBar`: ```tsx //... return
My custom appbar
{/* ... */}
``` All the props passed to the `AppBar` will be ignored if you define a custom component. #### Drawer The `Drawer` is the left-side menu of the CMS. It typically contains the navigation routes and the user menu. If you define a `Drawer` component, the `Scaffold` will automatically include a hamburger icon to open and close the drawer. If you don't include a drawer, the hamburger icon will not be displayed. The default drawer includes the navigation routes to your collections, as well as links to the admin views. ##### Props: - `className`: Additional classes to apply to the Drawer. - `style`: Additional styles to apply to the Drawer. - `children`: Define your own Drawer content. If you define children, the navigation routes will be ignored. ##### Custom drawer example You can replace the default Drawer by wrapping your custom component with the `Drawer`. Note that the burger icon will be displayed automatically if you define a custom Drawer. ```tsx //... return
My custom drawer
{/* ... */}
``` #### NavigationRoutes The `NavigationRoutes` component defines a `Routes` component (`react-router-dom`) that contains the routes to your home page, collections, custom views and admin views. It picks up all the configuration automatically from the `FireCMS` configuration. Note that you can also define your own routes if you need to. ##### Props: - `homePage`: Component to be displayed in the home page. If not provided, the default home page will be displayed. - `children`: Define your own routes. Note that these routes will be appended to the default routes. ##### Example: ```tsx //... return My custom home page}> {/* Define your custom routes here, using react-router */} } /> ``` Note that you can also define custom views by defining them in `useBuildNavigationController`, with the added benefit that they will be automatically included in the default drawer. #### SideDialogs The `SideDialogs` component is a container for side dialogs. Side dialogs are typically used to display forms or additional information in a side panel. You can access the `useSideDialogsController` hook to open and close side dialogs programmatically from your custom components. ### Utilities #### `useApp()` hook You can use the `useApp()` hook to access the `AppState` object from the context. This object contains the following properties: - `hasDrawer`: Whether the drawer is enabled. - `drawerHovered`: Whether the drawer is currently hovered. - `drawerOpen`: Whether the drawer is currently open. - `openDrawer`: Function to open the drawer. - `closeDrawer`: Function to close the drawer. - `autoOpenDrawer`: Whether the drawer should open on hover. - `logo`: Logo to be displayed in the top bar and drawer. ## Styling FireCMS FireCMS allows you to customize the look and feel of your admin panel. You can customize the theme, colors, and typography to match your brand. FireCMS uses tailwindcss for styling. FireCMS UI provides a default **preset** that can be extended or completely replaced. #### Customizing the theme Your default `tailwind.config.js` file should look like this: ```javascript export default { presets: [fireCMSConfig], content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", "./node_modules/@firecms/**/*.{js,ts,jsx,tsx}" ] }; ``` You may want to check the original [`tailwind.config.js`](https://github.com/firecmsco/firecms/blob/main/packages/ui/tailwind.config.js) file in the `@firecms/ui` package. You can extend the default theme by adding your customizations, including all supported tailwindcss features. #### Customizing colors ##### Primary and secondary colors FireCMS UI uses a primary color and a secondary color. You can customize these colors in your `index.css` file: ```css @import "@firecms/ui/index.css"; @tailwind base; @tailwind components; @tailwind utilities; :root { --fcms-primary: #0070F4; --fcms-primary-bg: #0061e610; --fcms-secondary: #FF5B79; } body { @apply w-full min-h-screen bg-gray-50 dark:bg-gray-900 flex flex-col items-center justify-center; } ``` These are the default values but you can change them to match your brand. The `--fcms-primary-bg` variable is used when hovering over primary elements, like cards ( it is usually mostly transparent) Just like in the previous section, feel free to take a look at the original [`index.css`](https://github.com/firecmsco/firecms/blob/main/packages/ui/src/index.css) file in the `@firecms/ui` package. ##### Customizing colors in the tailwind config If you need a more complex color customization, you can modify the `tailwind.config.js` file, to override the default color used by FireCMS. FireCMS defines colors for all the surfaces (a light gray by default), surfaces accents (a bluish gray by default), typography colors (primary, secondary and disabled) as well as the field colors (background, hover, etc). All these colors can be overridden in the `tailwind.config.js` file. This is an example of a config file that overrides the default colors: ```javascript export default { presets: [fireCMSConfig], content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", "./node_modules/@firecms/**/*.{js,ts,jsx,tsx}" ], theme: { extend: { colors: { "primary": "var(--fcms-primary)", "primary-bg": "var(--fcms-primary-bg)", "secondary": "var(--fcms-secondary)", "field": { "disabled": "rgb(224 224 226)", "disabled-dark": "rgb(35 35 37)" }, "text": { "primary": "rgba(0, 0, 0, 0.87)", "secondary": "rgba(0, 0, 0, 0.55)", "disabled": "rgba(0, 0, 0, 0.38)", "primary-dark": "#ffffff", "secondary-dark": "rgba(255, 255, 255, 0.60)", "disabled-dark": "rgba(255, 255, 255, 0.48)" }, "surface": { "50": "#f8f8fc", "100": "#E7E7EB", "200": "#CFCFD6", "300": "#B7B7BF", "400": "#A0A0A9", "500": "#87878F", "600": "#6B6B74", "700": "#454552", "800": "#292934", "900": "#18181C", "950": "#101013" }, "surface-accent": { "50": "#f8fafc", "100": "#f1f5f9", "200": "#e2e8f0", "300": "#cbd5e1", "400": "#94a3b8", "500": "#64748b", "600": "#475569", "700": "#334155", "800": "#1e293b", "900": "#0f172a", "950": "#020617" } } } } }; ``` #### Customizing typography FireCMS uses the `Rubik` font by default, both for headings and body text. It also uses the `JetBrains Mono` font for code blocks. ##### Adding new fonts If you would like to add a new font, you can install it using `npm` or `yarn`. For example, to add the `Noto Serif` font: ```bash yarn add @fontsource/noto-serif ``` and import it in your `App.tsx` file: ```tsx export default { presets: [fireCMSConfig], content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", "./node_modules/@firecms/**/*.{js,ts,jsx,tsx}" ], theme: { extend: { fontFamily: { sans: [ "Roboto", "Helvetica", "Arial", "sans-serif" ], headers: [ "Noto Serif", "Roboto", "Helvetica", "Arial", "sans-serif" ], mono: [ "JetBrains Mono", "Space Mono", "Lucida Console", "monospace" ] } } } }; ``` ##### Customizing typography styles Each typography style is defined in the `index.css`, and can be customized. These are the default styles, but you can change them to match your brand. The `@apply` directive is a tailwindcss feature that allows you to apply a set of utilities to a class. Feel free to add any of this classes to your `index.css` file, to override the default styles: ```css .typography-h1 { @apply text-6xl font-headers font-light; } .typography-h2 { @apply text-5xl font-headers font-light; } .typography-h3 { @apply text-4xl font-headers font-normal; } .typography-h4 { @apply text-3xl font-headers font-normal; } .typography-h5 { @apply text-2xl font-headers font-normal; } .typography-h6 { @apply text-xl font-headers font-medium; } .typography-subtitle1 { @apply text-lg font-headers font-medium; } .typography-subtitle2 { @apply text-base font-headers font-medium; } .typography-body1 { } .typography-body2 { @apply text-sm; } .typography-caption { @apply text-xs; } .typography-label { @apply text-sm font-medium; } .typography-button { @apply text-sm font-semibold uppercase; } ``` #### Do you miss any customization? If you need to customize any other aspect of FireCMS, please [let us know](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: ``` yarn run build && firebase deploy --only hosting ``` to deploy. ### 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. ## Firebase setup In order to run **FireCMS**, you need to create a Firebase project first, with some requirements: #### Firestore You need to enable **Firestore** in it. You can initialise the security rules in test mode to allow reads and writes, but you are encouraged to write rules that are suited for your domain. Keep in mind that rules should be defined for each collection and should be defined in a way that is suited for your domain. Please check the [Firestore documentation](https://firebase.google.com/docs/firestore/security/get-started) for more information. :::important In a default Firestore project, it is likely that your Firestore rules will not allow you to read or write to the database. Continue reading to learn how to set up your rules. ::: For example, a simple rule that allows any authenticated user to read and write to any collection would be: ``` rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth != null; } } } ``` but ideally you will want to make it more restricitve. For the products demo to work, your rules should look like this: ``` rules_version = '2'; service cloud.firestore { // everything is private by default match /{document=**} { allow read: if false; allow write: if false; } // allow every read to products collection but write only to authenticated users match /databases/{database}/documents { match /products/{id=**} { allow read: if true; allow write: if request.auth != null; } } // allow users to modify only their own user document match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; } } ``` #### Web app In the project settings, you need to create a **Web app** within your project, from which you can get your Firebase config as a Javascript object. That is the object that you need to pass to the CMS. ![firebase_setup](/img/firebase_setup_app.png) :::tip Firebase Hosting Note that you can also link your new webapp to **Firebase Hosting** which will allow you to deploy it with very little effort. You can create the Firebase Hosting site at a later stage and link it to your webapp. ::: #### Authentication You will most likely want to enable authentication in order to pass the login screen :::important Vite uses the default url `http://127.0.0.1:5173` for the development server. Firebase Auth will require to add this url to the authorized domains in the Firebase console. Alternatively, you can use the url `http://localhost:5173`. ::: ![firebase_setup](/img/firebase_setup_auth.png) #### Storage In case you want to use the different storage fields provided by the CMS, you need to enable **Firebase Storage**. The default bucket will be used to save your stored files. :::note If you are experiencing any CORS issues, you can enable CORS in your bucket settings as specified in: https://firebase.google.com/docs/storage/web/download-files#cors_configuration Create a file with the content: ``` [ { "origin": ["*"], "method": ["GET"], "maxAgeSeconds": 3600 } ] ``` and upload it with: `gsutil cors set cors.json gs://`. ::: ## Integrating MongoDB Atlas with FireCMS ## 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 ![user_management.png](/img/user_management.png) This document provides an overview of how to use the User Management Plugin with FireCMS to manage users and roles. The User Management Plugin allows you to create, edit, and delete users and roles within your FireCMS project. It ensures that users have the appropriate permissions based on their roles and integrates this authentication and permissions system directly into your backend. This plugin allows you to control which users can access your application and what actions they can perform over what specific 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 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`. - **usersLimit**: Maximum number of users that can be created. - **canEditRoles**: Determines if the logged-in user can edit roles. Default is `true`. - **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", canEditRoles: true, 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", usersLimit: 100, canEditRoles: true, allowDefaultRolesCreation: true, includeCollectionConfigPermissions: true, }); const { authLoading, canAccessMainView, notAllowedError } = useValidateAuthenticator({ disabled: userManagement.loading, authController, authenticator: userManagement.authenticator, dataSourceDelegate: firestoreDelegate, storageSource }); const navigationController = useBuildNavigationController({ collections, views, adminViews: userManagementAdminViews, collectionPermissions: userManagement.collectionPermissions, // Optional, link collection permissions to user management authController, dataSourceDelegate: firestoreDelegate }); 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. - **`usersLimit`**: Optional. Maximum number of users that can be created. - **`canEditRoles`**: Optional. Determines if the logged-in user can edit roles. Boolean value. - **`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 ![collection_editor.png](/img/collection_editor.png) 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: ```firestore 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 navigationController = useBuildNavigationController({ collections: collectionsBuilder(), views: customViews, adminViews: userManagementAdminViews, collectionPermissions: collectionEditorPlugin.collectionPermissions, authController, dataSourceDelegate: firestoreDelegate }); 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. - **`getPathSuggestions`**: Function to provide path suggestions during collection creation. - **`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 ![data_export.png](/img/data_export.png) This document provides an overview of how to use the **Data Export Plugin** with FireCMS to export collection data in JSON or CSV formats. The Data Export Plugin allows you to export data from your FireCMS collections easily. With this plugin, you can generate JSON or CSV files containing your collection data, which can be useful for data backups, migrations, or offline analysis. 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 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); }, }); return ( ); } 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 ![data_import.png](/img/data_import.png) 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); } }, }); ``` ## 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. **setDateToMidnight**: (Optional) Set the date to midnight. **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. ## 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 yarn create firecms-app --pro ``` or ```bash npx 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 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. ``` yarn add @mui/material @emotion/react @emotion/styled ``` If you need MUI icons, run: ``` yarn add @mui/icons-material ``` ## 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/pro/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 { --fcms-primary: #0070F4; --fcms-primary-bg: #0061e610; --fcms-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": "^10.12.2", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@originjs/vite-plugin-federation": "^1.3.5", "@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}", ], }; ``` ## Collections In FireCMS, **collections** represent groups of entities. They map to your database collections and are used to define the schema of your entities. Collections are the backbone of your CMS, and they are used to define the fields of your entities, the permissions, the filters, and the actions that can be performed on them. You can find collections at the **top level** of the navigation tree (the entries displayed in the home page and the navigation drawer), or as **subcollections**. Collections in FireCMS can be defined in two ways: - Using the **Collection editor UI**. - Using **code**. If the logged-in user has the required permissions, they will be able to create collections using the UI. Collections defined by the UI have some limitations, such as not being able to define any callbacks. On the other hand, if you are using code, you can define your collections programmatically, and you can use all the features of FireCMS., including defining custom callbacks, customizing the permissions, and defining custom fields and components. ### 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). ::: :::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 columns, use the column id. - 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). - **`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. ```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] } ``` - **`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] } ``` - **`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. 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). - **`views`**: 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/entity_views). ## 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 * `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 ![Custom entity view](/img/entity_view.png) 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 ![Custom entity view](/img/entity_view_secondary_form.png) 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: [ { path: "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 } }); ``` ## 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. You need to define a `FirestoreTextSearchControllerBuilder` and add it to your config. Typically, you will want to index your entities in some external solution, such as Algolia. For this to work you need to set up an AlgoliaSearch account and manage the indexing of your documents. You can achieve this by implementing a Google Cloud Function that listens to Firestore changes and updates the Algolia index. There is also a [Firebase extension](https://extensions.dev/extensions/algolia/firestore-algolia-search) for the very same purpose. The examples below show how to use Algolia as an external search provider, using the `algoliasearch` library version 5. #### 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; } }); export default function App() { const productCollection = buildCollection({ path: "products", name: "Products", textSearchEnabled: true, properties: { name: { dataType: "string", name: "Name", validation: { required: true } } } }); return ; } ``` #### 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 with 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 ![collection_actions](/img/collection_actions.png) 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. 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. ```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: {} }); ``` ##### 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. 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 * `collection`?: EntityCollection * `selectionController`?: SelectionController, used for accessing the selected entities or modifying the selection * `highlightEntity`?: (entity: Entity) => void * `unhighlightEntity`?: (entity: Entity) => void * `onCollectionChange`?: () => void * `sideEntityController`?: SideEntityController ### Example 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 to that will run some business logic, in the backend. In this example, we will use the `fetch` API to call the function. ```tsx export const productsCollection = buildCollection({ id: "products", path: "products", // other properties entityActions: [ { icon: , name: "Archive", collapsed: false, onClick({ entity, collection, 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" }); }); } } ], }); ``` ## 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 ![Textfield](/img/fields/Textfield.png) 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 ![Textfield](/img/fields/Multiline_textfield.png) 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 ![Textfield](/img/fields/Markdown.png) 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 [`MarkdownFieldBinding`](https://firecms.co/docs/../api/functions/MarkdownFieldBinding). #### Url text field ![Textfield](/img/fields/Url.png) 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 ![Field](/img/fields/Email.png) 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 ![Field](/img/fields/Select.png) 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 ![Field](/img/fields/Multi_select.png) 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 ![Field](/img/fields/File_upload.png) ```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 ![Field](/img/fields/Multi_file_upload.png) ```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 ![Field](/img/fields/Switch.png) 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 ![Field](/img/fields/Date.png) ```typescript jsx buildProperty({ dataType: "date", name: "Expiry date", mode: "date" }); ``` ##### Date/time field ![Field](/img/fields/Date_time.png) ```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 ![Field](/img/fields/Reference.png) ```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 ![Field](/img/fields/Multi_reference.png) ```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 ![Field](/img/fields/Group.png) 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 ![Field](/img/fields/KeyValue.png) 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 ![Field](/img/fields/Repeat.png) 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 }); ``` 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 ![Field](/img/fields/Block.png) 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) * `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 can be used with many form fields, from simple text fields, to select fields, markdown or file uploads (the storage key, or the url gets saved). ```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. * `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` ```tsx const imageProperty = buildProperty({ dataType: "string", storage: { mediaType: "image", storagePath: (context) => { return "images"; }, acceptedFiles: ["image/*"], fileName: (context) => { return context.file.name; } } }); ``` #### `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", }); ``` #### `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. - [`MarkdownFieldBinding.`](https://firecms.co/docs/../api/functions/MarkdownFieldBinding) 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. #### `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 } } } }); ``` #### `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`. #### `expanded` Should the map be expanded by default in the form view. Defaults to `false`. #### `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. #### `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. In the builder you receive [`PropertyBuilderProps`](https://firecms.co/docs/api/type-aliases/PermissionsBuilderProps) and return your property. 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 allow you to customize the field that it is displayed in the form, related to a specific property. These fields use your own logic to render the corresponding view, and are responsible for updating the underlying property value. If you need a custom field for your property you can do it by passing a React component to the `propertyConfig` prop of a property `config`. The React component must accept the props of type [`FieldProps`](https://firecms.co/docs/api/interfaces/FieldProps). The bare minimum you need to implement is a field that displays the received `value` and uses the `setValue` callback. You can also pass custom props to your custom field, which you then receive in the `customProps` prop. If you are developing a custom field and need to access the values of the entity, you can use the `context` field in FieldProps. You can check all the props [`FieldProps`](https://firecms.co/docs/api/interfaces/FieldProps). ### Example This is an example of a custom TextField that takes the background color as a prop ```tsx // you can define the props that your custom field will receive interface CustomColorTextFieldProps { color: string } export default function CustomColorTextField({ property, value, setValue, setFieldValue, customProps, touched, includeDescription, showError, error, isSubmitting, context, // the rest of the entity values here ...props }: FieldProps) { const { mode } = useModeController(); const backgroundColor = customProps?.color ?? (mode === "light" ? "#eef4ff" : "#16325f"); return ( <> { setValue( evt.target.value ); }}/> ); } ``` ...and how it is used: ```tsx export const blogCollection = buildCollection({ id: "blog", path: "blog", name: "Blog entry", 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" // pass the custom prop } } } }); ``` ## Custom previews Every property you define in the CMS as 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 Firestore 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. 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: A quick example for a custom view: ```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: customViews }; export default function App() { return ; } ``` Your custom view is implemented as any regular React component that uses some hooks provided by the CMS: ```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` For state and operations regarding authentication. The props provided by this hook are: * `user` The Firebase user currently logged in or null * `authProviderError` Error dispatched by the auth provider * `authLoading` Is the login process ongoing * `loginSkipped` Is the login skipped * `notAllowedError` The current user was not allowed access * `skipLogin()` Skip login * `signOut()` Sign out Example: ```tsx export function ExampleCMSView() { const authController = useAuthController(); 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 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: * `dateTimeFormat`?: Format of the dates in the CMS. Defaults to 'MMMM dd, yyyy, HH:mm:ss' * `locale`?: Locale of the CMS, currently only affecting dates * `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 * `authController`: Used auth controller * `entityLinkBuilder`?: Builder for generating utility links for entities * `userConfigPersistence`: Use this controller to access data stored in the browser for the user * `snackbarController`: Use this controller to display snackbars ## useDataSource Use this hook to get the datasource being used. This controller allows you to fetch and save data from the datasource (such as Firestore) using the abstraction of collection and entities created by FireCMS. * `fetchCollection`: Fetch data from a collection * `listenCollection`: Listen to a entities in a given path. * `fetchEntity`: Retrieve an entity given a path and a schema * `listenEntity`: Get realtime updates on one entity. * `saveEntity`: Save entity to the specified path * `deleteEntity`: Delete an entity * `checkUniqueField`: Check if the given property is unique in the given collection ## useStorageSource Use this hook to get the storage source being used. 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 * `uploadFile`: Upload a file, specifying a name and a path * `getDownloadURL`: Convert a storage path into a download url ## useModeController Hook to retrieve the current mode (`light` | `dark`), and `setMode` or `toggle` functions to change it. Consider that in order to use this hook you need to have a parent `FireCMS` component in the tree. #### Props: ```tsx { mode: "light" | "dark"; setMode: (mode: "light" | "dark") => void; toggleMode: () => void; } ``` ## Building a blog in FireCMS Cloud ![blog_example](/img/blog_example.webp) :::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 ![Product selection](/img/product_selection.webp) 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: ![dynamic_navigation_collection](/img/recipes/dynamic_navigation_collection.png) And each of those documents will generate a new navigation item. In this case we will have 3 navigation items, one for each unit: ![dynamic_navigation_home](/img/recipes/dynamic_navigation_home.png) #### 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 }) => { 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 ] }; ``` :::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 ![Entity Callbacks](/img/recipes/entity_callbacks.png) 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. ![entity_callbacks_example](/img/recipes/entity_callbacks.png) 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. ![entity_callbacks_delete_example](/img/recipes/entity_callbacks_delete.png) 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!
## 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 `onValueChange` 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 (
); } ``` ### 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"`, `"neutral"`, 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"`, `"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 ( <> Search Search in your documents ); } ``` ### 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 ( <> Your dialog Full-Screen Dialog Content ); } ``` ### 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 ( <>
Scrollable Dialog Content
); } ``` ### 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 ( <> Your dialog Dialog with Custom Width ); } ``` ## 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 ( Open Menu}> alert("Menu Item 1 clicked")}>Menu Item 1 alert("Menu Item 2 clicked")}>Menu Item 2 alert("Menu Item 3 clicked")}>Menu Item 3 ); } ``` ### 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 ( setOpen(true)}>Click me }> alert("Action 1")}>Action 1 alert("Action 2")}>Action 2 alert("Action 3")}>Action 3 ); } ``` ### 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 ( Dense Menu}> alert("Dense Item 1 clicked")}>Dense Item 1 alert("Dense Item 2 clicked")}>Dense Item 2 alert("Dense Item 3 clicked")}>Dense Item 3 ); } ``` ## 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 A text field that supports multiline inputs, suitable for comments or notes: ```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 rows={4} /> ); } ``` ### Text Field Sizes Examples of different size options for the text field component: ```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" />
); } ``` ### Text Field with Adornment A text field featuring an end adornment for icons, action buttons, or informative text: ```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={@} /> ); } ``` ### Text Field Props The `TextField` component in FireCMS UI is highly customizable through various props. Below is a comprehensive list of props you can use to tailor the `TextField` to your needs: - `value`: The current value of the text field. Required. - `onChange`: Handler function called when the text field value changes. Required. - `label`: The label displayed above the text field. - `placeholder`: Placeholder text displayed when the text field is empty. - `multiline`: If `true`, the text field will allow multiline input. Defaults to `false`. - `rows`: Specifies the number of visible text lines for a multiline text field. - `variant`: The variant to use for the text field. Options are `'standard'`, `'outlined'`, or `'filled'`. Defaults to `'standard'`. - `fullWidth`: If `true`, the input will take up the full width of its container. Defaults to `false`. - `size`: The size of the text field. Options are `'small'` or `'medium'`. Defaults to `'medium'`. - `color`: The color of the text field. Options are `'default'`, `'primary'`, or `'secondary'`. Defaults to `'default'`. - `type`: The type of input element; e.g., `password`, `text`, `email`, etc. Defaults to `'text'`. - `disabled`: If `true`, the text field will be disabled. Defaults to `false`. - `helperText`: Text that appears below the text field. - `error`: If `true`, the text field will have an error state. Defaults to `false`. - `startAdornment`: Element to be placed at the start of the text field. - `endAdornment`: Element to be placed at the end of the text field. ## 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 ( ); } ``` ## Changelog ### [3.0.0-beta.12] - 2025-12- - **Full-screen entity views**: You can now open entities in a full-screen view. This is useful when you want to focus on the entity you are editing. You can enable this feature by setting the `openEntityMode` prop to `full_screen` in the collection view. The default mode continues to be `side_panel`. There has been a big navigation revamp to accomodate all the new use cases. - **Scroll preservation**. When you open an entity in a full-screen view, the scroll position of the collection view is preserved. - **Drafts saved locally**: Drafts are now saved locally in the browser. This means that if you close the browser and open it again, the draft will still be there. This will help prevent losing data if you accidentally close the browser, or you navigate away. - The state of filters ans sorting is now preserved in the URL. - You can now override custom entity actions. Just provide an action with one of the keys `edit`, `copy` or `delete` in the `entityActions` prop in the collection view. This will override the default actions for the entity. ### [3.0.0-beta.11] - 2024-12-13 - New Next.js template for FireCMS PRO. You can now create a new project with the PRO template using the CLI. - [BREAKING] Removed `userRoles` from AuthController. You can now access the `roles` prop in the user object directly - [BREAKING] Many FireCMS UI sizes have been adjusted for better consistency. This will affect you only if you are using custom components. - `smallest` or `tiny` have been renamed to `small`. - `small` has been renamed to `medium`. - `medium` has been renamed to `large`. - [BREAKING] 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 }); ``` - Added many "use client" directives to UI components. - Fixed issues in collection editor code dialog. - Updated web styles and integrated improvements in Docusaurus. - Enhanced styling for empty references and minor design tweaks. - Continued work in progress on Editor custom components. - Reintroduced dark primary color variant for better theme options. - Minor web updates for improved aesthetics and functionality. - Fixed a bug where the Editor was not saving false values. - Replaced all instances of gray and slate colors with more unified `surface` and `surface-accent` colors for UI consistency. - Added Avatar component fallback and integrated ESLint configuration into templates. - Enhanced error handling in forms and improved cloud error messages. - Refactored user management logic for better code organization. - Improved the handling of boolean switch properties in configurations. - Introduced state management for children in ArrayContainer. - Added a recipe for slug creation, improving URL handling and SEO. - Fixed crash issues in repeat fields for subproperties and addressed various minor styling and functionality bugs. - Made improvements to heatmap responsiveness (HMR fixes). - Refactored text search functionalities for better efficiency and added relevant documentation. - Fixed issues with number input fields blocking scroll and replaced date picker with native HTML date input for consistency. - If you are using the `Select` component, you don't need to provide a `renderValue` function anymore. The component will handle it automatically. - Custom preview properties are now rendered if the value is undefined. - Fixed for Cloud version refreshing navigation too often. - Fix for local search not working when returning to a collection. - Fix for bug when selecting a read only entity. - Fixed selection bug in collection groups for entities sharing id. - Reference previews now take into account arrays of images for the preview image. ### [3.0.0-beta.10] - 2024-07-10 - Fixed issues with wrong licenses. - Resolved TipTap dependencies. - Addressed various minor styling updates across the web. - Moved body CSS from default imports to individual files for better modularity. - Implemented several web updates, including select style fixes and dialog title adjustments for text search. - Updated the collection editor property select view and improved widget selection layout. - Applied AppBar tweaks to enhance behavior on mobile devices. - Improved console outputs and cleaned up miscellaneous code segments. - Enhanced UI with the addition of a Slider component and updated related documentation. - Replaced entity edit icon with a pencil for clarity. - Updated dependencies and refined project management with a license check feature. - Improved Formex handling of number inputs and fixed DateTimeField export in Next.js. - Added API key generation and project selection capabilities. - Introduced a past-due warning message and improvements in collection and subcollection data handling. - Provided better error handling and layout consistency in the application. ### [3.0.0-beta.9] - 2024-07-10 - **NEW MARKDOWN EDITOR**: The markdown editor has been completely revamped. It now supports a live preview, and a much improved editing experience. It now includes a slash menu you can access by typing `/` in the editor. Also a new toolbar with buttons for common markdown operations. The new editor also includes an AI auto-complete feature, that suggests markdown elements as you type, and displays the generated markdown in real time, and highlighted. - Additional fields are also now displayed in the entity side dialog. - Import/export is now broken into 2 separate plugins. - Packages now are not minified, leaving that responsibility to the client bundler. - Added max size field in the collection editor for files. - Improved error handling of wrong file uploads. - Improving error when opening a non accessible entity in the side view. - Select component tweaks and removed `multiple` prop. - New `MultiSelect` component with a much improved UX. - Introduced AppCheck directly in FireCMS Cloud. - Added MongoDB support for FireCMS PRO. - Multiple fixes in the user management plugin for PRO projects. - Updated react-router dependencies. - Improved customization, you can now define the styles for each typography entry, including font size, typography... - Improved home page search, now using fuse.js - Fix for missing index and wrong keys in array of maps with property builder. - Fix for drag handle position in editor. - Renamed `partOfBlock` to `minimalistView` in field props. - It is now possible to define preview properties at the collection level. - Updated references styling. - Tooltips have been revamped to use less divs. - Fix for data enhancement plugin position. - Fix for how you can override the data source for specific collections. - You can now also define a different database other than `(default)` in the data source. - User Management plugin now saves users with the email as key, instead of a random value. - Fix for side panels adjusting to the right size when window changes size. - Some drawer styling updates. - `RepeatFieldBinding` can now use unresolved array properties. ### [3.0.0-beta.8] - 2024-07-10 - Fix for excessive re-renders in the form view. - You can now use `PropertyFieldBinding` components in your custom entity views, and they will be treated as regular fields. - For additional entity views, you can now preserve the bottom actions bar, with the prop `includeActions`. - For map properties, if they are not required, the value might me `undefined`, but if a child property has a value, validation will be triggered for all children. - Fix for data maps not getting traversed correctly with null value. - CLI pro template now supports creating web app config. - Fix for collection editor data inference for enums. - Small Sheet styling improvement. - Fixed local search loading issue with cached data. - Small visual fix for IDs. - AppCheck updates. - Fixed inconsistent opening of reference preview side dialogs. - Fixed icons for image previews. - Navigating to home URL when logging out. - Added `previewUrl` prop in storage options (#639). - Fixed XLSX security issue CVE-2024-22363 (#654). - Fix for the removal of keys in KeyValue fields. - Added large size for boolean switches. - Updated eslint to the latest version and config. - Types fix for `removePropsIfExisting`. - Fix for video drag bug in array fields. - Added option to ask for password reset, in PRO login view - Allowing null default values for properties. - Added count to array field bindings. - Fixed default values in nested maps in arrays. - Resolving entity collection path with the one coming from the entity, not the view config. - Small fix for logo image. - Fixed conditional fields not updating correctly. - Hide new user button if `disabledSignupScreen`. - Improved docs navigation bar styling. - Allowing maps to be completely undefined. - Disabled add button in collection groups. - Big entity refactor, custom views are now under the formex provider. - CLI fix for not logged in users. - Fix for datamaps not getting traversed correctly with null values. - Scaffold prop updates. ### [3.0.0-beta.7] - 2024-06-18 - Renamed the `cn` utility class to `cls`, while keeping `cn` available with a deprecation warning. - Added Menubar documentation and missing skeleton docs. - Corrected properties order type to allow subcollections. - New UI section added to the landing page. - Improved saving and closing dialog flow. - Allow hiding IDs and entity links in references and previews. - Removed some CSS transitions. - Allow hiding the color mode toggle. - Added JSON view example. - Changed virtual table to use size in pixels. - Some design updates for better user experience. - Added back collection group column with parent IDs. - Improved empty results output. - Added sample prompts and suggestions for DataTalk. - Enhanced side entity view, dynamically calculated based on collection property depth. - Fixed mergeDeep types. - Fixed issue with exporting non-existing properties defined in `propertiesOrder`. - Fixed PRO template issues without Cloud projects. - Improved handling for enum values with value 0. ### [3.0.0-beta.6] - 2024-04-23 - Added AppCheck to every FireCMS variant. - Various fixes for datasource delegate. - Fix in saving cleaned data. - Cloud new user roles creation issue fixed. - Error message display issue in table cells fixed. - Subcollections updating issue fixed. - Import/export analytics and related data mapping conversions updated. - Updated and improved handling of user roles and permissions. - Enhanced the handling of service account files and project creation using SA. - Updated the behavior of unindexed queries. - User management connection to demo removed. - Dependency updates to mitigate security issues. - Exposing additional methods from data inference for better customization. - Pro template updates for improved UI/UX. - Updated documentation for collections and user management. ### [3.0.0-beta.5] - 2024-04-01 - [BREAKING] The main component for FireCMS Cloud has been renamed from `FireCMSApp` to `FireCMSCloudApp`. Please update your imports accordingly. - Fixes related to the CLI. You can now install the CLI globally with `npm install -g @firecms/cli`. ### [3.0.0-beta.4] - 2024-03-27 - [BREAKING] The package name for FireCMS Cloud has changed from `firecms` to `@firecms/cloud`. This is done to avoid conflicts with the main FireCMS package. If you are using FireCMS Cloud, you will need to update your imports. - [BREAKING] If you are importing the tailwind configuration, you can now find the import at: `import fireCMSConfig from "@firecms/ui/tailwind.config.js";` - [BREAKING] In that case, you also need to add `@tailwindcss/typography` to your dev dependencies. - [BREAKING] You need to update your `vite.config.js` and replace the package name in the federated configuration: ```javascript import { defineConfig } from "vite" import react from "@vitejs/plugin-react" import federation from "@originjs/vite-plugin-federation" // 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"] }) ], build: { modulePreload: false, target: "ESNEXT", cssCodeSplit: false, } }) ``` - Minor performance improvements and bug fixes. - Enhanced filtering and sorting capability for indexed fields. - Extended StorageSource to support custom `bucketUrl`. - Cleanup for navigation controller generics and Markdown prose classes. - Addressed User Management saving issues and renamed Cloud template. - Fixed ReferenceWidget.tsx rerenders. - Fixed homepage new collection button issue. - Fixed CLI templates path. - Roles integrated into AuthController. - Small change to plugins API. - Added user details to navigation bar dropdown. - Dependencies updated. - Entity view preview and title refactor. - Kanban board work in progress. - Fix for new radix empty select values. - Fixes for undefined properties in arrays and editor. - Additional parameters added in auth controllers. - Navigation cards refactor and Plugin API cleanup. - Fix for importing data with non-string IDs. - Documentation: Added recipe for managing entity callbacks. - Web updates and CLI fix for yarn. ### [3.0.0-beta.3] - 2024-02-21 - Fix for importing data in subcollections. - Code reordering. - Removed minification. Changed EntityReference type checks. - Editor image upload updates. - Cosmetic. - Moved tailwind.config.js editor plugin. - Removed callbacks in side navigation views, prevents bug. - PRO template fix. - PRO Login view cleanup. ### [3.0.0-beta.2] - 2024-02-21 - Added Formex package to handle forms across the platform. Formex is an in-house form management library with a similar API to Formik, but with better performance, and much more lightweight. - Enhanced onboarding process for new users. - Fixed data import issues for new collections. - Tweaked SaaS onboarding for better user experience. - Implemented regexp validation for input fields. - Improved login error feedback. - Extracted navigation controller for better manageability. - Updated styles for consistency. - Updated Vite and dependencies for performance and security. - Refactored user and role forms to use Formex. - Fixed table header forms and collection editor issues. - Addressed incorrect JSON import problems. - Removed Formik, enhancing form management with Formex. - Made minor HTML nesting and debounce fixes. - Fixed array container menu and multiline input bugs. - Migrated Tailwind configuration to lib for easier management. - Adjusted Sentry configuration for error reporting. - Fix for subcollections edit view showing empty. - Fixes for block and group properties in editor saving multiple entries when editing an existing sub property. ### [3.0.0-beta.1] - 2024-02-01 The first beta release of FireCMS v3.0.0. Check all the new features and improvements in the [documentation](https://firecms.co/docs/what_is_new_v3) and the [migration guide](https://firecms.co/docs/docs/migrating_from_v2). ### [2.2.0] - 2023-11-09 - Fix for missing subcollection links. - New email and password login flow - Removed add button in collection group - Export fixes - Fix for collections search ### [2.1.0] - 2023-09-12 - [BREAKING] The logic to verify valid filter combinations has been moved to the `DataSource` interface. This improves the ability to customize the data source and allows for more complex filters. This change will only affect you if you have implemented a custom data source. You will need to add a `isFilterCombinationValid` method to your data source. - [BREAKING] The prop `filterCombinations` has been removed from the `EntityCollection` component. This is now handled by the data source. If you need to allow multiple filters, you can use the new `FireStoreIndexesBuilder` callback. Check the [documentation](https://firecms.co/docs/collections/multiple_filters) for more information. - You can now use nested `spreadChildren` in map properties, allowing to show arbitrary nested structures as single columns in the collection view. - The collection count value is now updated with filters applied. - Fix for csv export not working when underlying data is invalid. - Fix for bug of collection search returning a single result. - Fix for reference fields breaking with incorrect values. ### [2.0.5] - 2023-07-11 - Default value for string properties is now `null` instead of `""`. - Fix for changing text search controller not updating as a dependency. - Fix for setting a unique field using a reference, which was generating an invalid query in Firestore. ### [2.0.4] - 2023-06-15 - Fix for `forceFilter` not being applied correctly in reference views. - Fix for nullable enum validation config. ### [2.0.3] - 2023-06-15 - Fix for form resetting values when saving. ### [2.0.2] - 2023-06-14 - Replaced `flexsearch` with `js-search`. Their imports are too messed up. - Fix for form assigning wrong ids - ### [2.0.1] - 2023-06-12 - Fix for block entries not generating the correct default value when adding a new entry. This was causing a bug when the child property is an array, like in the blog example. - Added the `formAutoSave` to collections. This removes the buttons from the form and automatically saves the entity when there are changes or the user leaves the form. - You can now access the `formContext` from collection views, allowing you to access the current entity being edited, modify values and `save`. ### [2.0.0] - 2023-06-07 - You can use a callback to define the default view of an entity now. - Fix when opening entities from a custom view, that also uses subcollections. ### [2.0.0-rc.2] - 2023-06-05 - `@mui/x-date-pickers` dependency reverted to `^5.0.0` - Assigned default values to every property now, based on the property type. e.g. boolean properties will have a default value of `false`, maps to `{}`, and most other properties to `null`. - Removed empty space for hidden properties in the entity side dialog. ### [2.0.0-rc.1] - 2023-05-31 - Added arbitrary key-value fields with the prop `keyValue` in map properties - `@mui/x-date-pickers` dependency updated (you may need to bump your version to 6.5.0) - Some enhancements to the `EntityCollectionTable` component, referring to values being updated in the background. Also correct debouncing for table fields. ### [2.0.0-beta.7] - 2023-05-23 - Added support for collection groups - [BREAKING] The `countEntities` function in the data source now takes an object instead of a string as parameter. This will only affect you if you have built a custom component using that function. - Added string url previews to fields - Fix for geopoints not being serialized correctly when saving. ### [2.0.0-beta.6] - 2023-05-11 - Fix for Typescript types not being exported correctly and giving errors when using the library with the quickstart. - Fix for error messages not showing up correctly in new text inputs. - Fix for flexsearch import causing crash using webpack ### [2.0.0-beta.5] - 2023-04-28 - Updated fields Look and Feel. Text fields are now custom, not the ones provided by Material UI. This allows for more customization, less code, and better performance. - Fixed login view not centered - Fixed popup field selection and drag and drop bug - Fix for skip login field - HTML now rendered correctly in markdown previews - Fix for `read` permission not being applied correctly. - Fix for not centered empty view state in collections ### [2.0.0-beta.4] - 2023-03-30 - Fixed table header bug - Added search bar in home page - Added favourites and recent collections view in home page. - Fix for some deeply nested property builders in arrays - Added `autoOpenDrawer` prop, allowing to open the drawer automatically when hovering the menu. - Allow choosing which custom view or subcollection is opened by default, with the `defaultSelectedView` prop. Thanks to @SeeringPhil for the PR! - Renamed `builder` to `Builder` in collection custom views for consistency. ### [2.0.0-beta.3] - 2023-03-21 - Fixed bug regarding custom selection controllers. - Fix for default value not being set in array properties. - Enabled Firebase App Check. Thanks to @sengerts for the PR! - Added copy function to array views. Thanks to @guustmc for the PR! - The entity side dialog is now wider by default. - Small improvements to block properties. Now the first type is selected by default. - Fixed additional ordering added when multiple filter applied, which created a bug. Thanks to @juanleondev for the PR! - Renamed `ReferenceSelectionView` to `ReferenceSelectionInner` - Added reference filters - Fixed delay of table update when deleting an entity - You can now change the value of any property within a custom field. ### [2.0.0-beta.2] - 2023-01-30 - Fixed bug where collection actions were getting their internal state reset. - Improved preview of files that are not images, videos, or audio files. - Form optimizations - Fix for reference dialog not clearing selection - Fix for multiple error snackbar, when there is an error uploading a file. - Fix for missing highlight when closing side dialog. - Fix for delayed data update when changing filters. - Internal refactoring of the `EntityCollectionTable` component. - [BREAKING] In the component `EntityCollectionTable`, the prop `ActionsBuilder` has been replaced with `actions`. ### [2.0.0-beta.1] - 2023-01-18 This is the first beta release of FireCMS v2.0.0. While still in beta, we consider this version stable enough to be used in production. > All changes related to V2 alpha are currently bundled in these documents: > - [What's new in version 2.0.0](https://firecms.co/docs/new_in_v2) > - [Migration guide from version 1.x to 2.0.0](https://firecms.co/docs/migrating_from_v1) > The changelog for 1.0.0 versions and previous versions can be > found [here](https://firecms.co/docs/1.0.0/changelog)