Skip to main content
Version: 1.0.0

Quickstart

note

Please note that in order to use FireCMS as described in this quickstart guide, you need an existing Firebase project with features enabled, such as Google authentication, Firestore and Firebase storage. Check the Firebase setup section if you need additional support

Let's build a very simple CMS that creates a collection of products, with some properties. It includes samples of some advanced features, such as dynamic conditional fields or references (to the same products' collection, for simplicity).

We are defining our Product type for better type checking and code clarity, but it is not compulsory.

Authentication and authorization are also enabled, and make use of the extra field in the authController to check for permissions.

Steps

  • Create a new React app including Typescript:
yarn create react-app my-cms --template typescript
  • Go into the new directory:
cd my-cms
  • Install FireCMS and it's peer dependencies:
yarn add @camberi/firecms firebase@^9 @mui/material@^5 @mui/icons-material@^5 @mui/lab @mui/styles@^5 @mui/x-date-pickers@^5.0.0-beta.1 @emotion/react @emotion/styled react-router@^6 react-router-dom@^6

You can replace the content of the file App.tsx with the following sample code.

Remember to replace the Firebase config with the one you get after creating a webapp in the Firebase console.

If you don't know where to find the Firebase app config, check the Firebase setup section

import React from "react";

import { User as FirebaseUser } from "firebase/auth";
import {
Authenticator,
buildCollection,
buildProperty,
buildSchema,
EntityReference,
FirebaseCMSApp,
NavigationBuilder,
NavigationBuilderProps
} from "@camberi/firecms";

import "typeface-rubik";
import "typeface-space-mono";

// TODO: Replace with your config
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};

const locales = {
"en-US": "English (United States)",
"es-ES": "Spanish (Spain)",
"de-DE": "German"
};

type Product = {
name: string;
price: number;
status: string;
published: boolean;
related_products: EntityReference[];
main_image: string;
tags: string[];
description: string;
categories: string[];
publisher: {
name: string;
external_id: string;
},
expires_on: Date
}

const productSchema = buildSchema<Product>({
name: "Product",
properties: {
name: {
title: "Name",
validation: { required: true },
dataType: "string"
},
price: {
title: "Price",
validation: {
required: true,
requiredMessage: "You must set a price between 0 and 1000",
min: 0,
max: 1000
},
description: "Price with range validation",
dataType: "number"
},
status: {
title: "Status",
validation: { required: true },
dataType: "string",
description: "Should this product be visible in the website",
longDescription: "Example of a long description hidden under a tooltip. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin quis bibendum turpis. Sed scelerisque ligula nec nisi pellentesque, eget viverra lorem facilisis. Praesent a lectus ac ipsum tincidunt posuere vitae non risus. In eu feugiat massa. Sed eu est non velit facilisis facilisis vitae eget ante. Nunc ut malesuada erat. Nullam sagittis bibendum porta. Maecenas vitae interdum sapien, ut aliquet risus. Donec aliquet, turpis finibus aliquet bibendum, tellus dui porttitor quam, quis pellentesque tellus libero non urna. Vestibulum maximus pharetra congue. Suspendisse aliquam congue quam, sed bibendum turpis. Aliquam eu enim ligula. Nam vel magna ut urna cursus sagittis. Suspendisse a nisi ac justo ornare tempor vel eu eros.",
config: {
enumValues: {
private: "Private",
public: "Public"
}
}
},
published: ({ values }) => buildProperty({
title: "Published",
dataType: "boolean",
columnWidth: 100,
disabled: (
values.status === "public"
? false
: {
clearOnDisabled: true,
disabledMessage: "Status must be public in order to enable this the published flag"
}
)
}),
related_products: {
dataType: "array",
title: "Related products",
description: "Reference to self",
of: {
dataType: "reference",
path: "products"
}
},
main_image: buildProperty({ // The `buildProperty` method is an utility function used for type checking
title: "Image",
dataType: "string",
config: {
storageMeta: {
mediaType: "image",
storagePath: "images",
acceptedFiles: ["image/*"]
}
}
}),
tags: {
title: "Tags",
description: "Example of generic array",
validation: { required: true },
dataType: "array",
of: {
dataType: "string"
}
},
description: {
title: "Description",
description: "Not mandatory but it'd be awesome if you filled this up",
longDescription: "Example of a long description hidden under a tooltip. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin quis bibendum turpis. Sed scelerisque ligula nec nisi pellentesque, eget viverra lorem facilisis. Praesent a lectus ac ipsum tincidunt posuere vitae non risus. In eu feugiat massa. Sed eu est non velit facilisis facilisis vitae eget ante. Nunc ut malesuada erat. Nullam sagittis bibendum porta. Maecenas vitae interdum sapien, ut aliquet risus. Donec aliquet, turpis finibus aliquet bibendum, tellus dui porttitor quam, quis pellentesque tellus libero non urna. Vestibulum maximus pharetra congue. Suspendisse aliquam congue quam, sed bibendum turpis. Aliquam eu enim ligula. Nam vel magna ut urna cursus sagittis. Suspendisse a nisi ac justo ornare tempor vel eu eros.",
dataType: "string",
columnWidth: 300
},
categories: {
title: "Categories",
validation: { required: true },
dataType: "array",
of: {
dataType: "string",
config: {
enumValues: {
electronics: "Electronics",
books: "Books",
furniture: "Furniture",
clothing: "Clothing",
food: "Food"
}
}
}
},
publisher: {
title: "Publisher",
description: "This is an example of a map property",
dataType: "map",
properties: {
name: {
title: "Name",
dataType: "string"
},
external_id: {
title: "External id",
dataType: "string"
}
}
},
expires_on: {
title: "Expires on",
dataType: "timestamp"
}
}
});

const localeSchema = buildSchema({
customId: locales,
name: "Locale",
properties: {
title: {
title: "Title",
validation: { required: true },
dataType: "string"
},
selectable: {
title: "Selectable",
description: "Is this locale selectable",
dataType: "boolean"
},
video: {
title: "Video",
dataType: "string",
validation: { required: false },
config: {
storageMeta: {
mediaType: "video",
storagePath: "videos",
acceptedFiles: ["video/*"]
}
}
}
}
});

export default function App() {

const navigation: NavigationBuilder = async ({
user,
authController
}: NavigationBuilderProps) => {

return ({
collections: [
buildCollection({
path: "products",
schema: productSchema,
name: "Products",
permissions: ({ authController }) => ({
edit: true,
create: true,
// we have created the roles object in the navigation builder
delete: authController.extra.roles.includes("admin")
}),
subcollections: [
buildCollection({
name: "Locales",
path: "locales",
schema: localeSchema
})
]
})
]
});
};

const myAuthenticator: Authenticator<FirebaseUser> = async ({
user,
authController
}) => {
// You can throw an error to display a message
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 user extra field.
const sampleUserData = await Promise.resolve({
roles: ["admin"]
});
authController.setExtra(sampleUserData);
return true;
};

return <FirebaseCMSApp
name={"My Online Shop"}
authentication={myAuthenticator}
navigation={navigation}
firebaseConfig={firebaseConfig}
/>;
}

Then simply run:

yarn start

You should be able to see your FireCMS instance in your browser, awesome!

Sign up to our newsletter to get the latest news and updates. No spam!