CMS configuration
The CMS is added to a site with the cmsPlugin() plugin. To set it up for the first time, see Getting started. This page covers the options you're most likely to need.
Plugin options
Only firebaseConfig is required. Common options include:
-
id: namespaces the site's content in Firestore, so several sites can share one Firebase project. Defaults todefault. -
name: the site name shown in the CMS. -
firebaseConfig: the web app config from the Firebase console. -
gapi: a Google API key and OAuth client id, for the Google Drive and Sheets features. -
ai: the models available to Root AI. -
isUserAuthorized: a custom check for whether a user can access the CMS.
See CMSPluginOptions in the API reference for every option.
// @/root.config.ts
import {defineConfig} from '@blinkk/root';
import {cmsPlugin} from '@blinkk/root-cms/plugin';
export default defineConfig({
plugins: [
cmsPlugin({
id: 'my-site',
name: 'My Site',
firebaseConfig: {
apiKey: '...',
authDomain: 'my-project.firebaseapp.com',
projectId: 'my-project',
storageBucket: 'my-project.appspot.com',
},
}),
],
});
Security rules and roles
Content access is controlled by Firestore security rules and a list of roles per site. The easiest way to set both up is with init-firebase, which applies the rules and makes you an ADMIN:
pnpm exec root-cms init-firebase --admin=you@example.com
To apply the rules by hand, paste them into the Rules tab of the Firestore page in the Firebase console:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /Projects/{project} {
allow write:
if isSignedIn() && userIsAdmin();
allow read:
if isSignedIn() && userCanRead();
match /{collection}/{document=**} {
allow write:
if isSignedIn() && userCanPublish();
allow read:
if isSignedIn() && userCanRead();
}
match /Collections/{collectionId}/Drafts/{document=**} {
allow write:
if isSignedIn() && userCanEdit();
}
function isSignedIn() {
return request.auth != null;
}
function getRoles() {
return get(/databases/$(database)/documents/Projects/$(project)).data.roles;
}
function userCanRead() {
let roles = getRoles();
let email = request.auth.token.email;
let domain = '*@' + email.split('@')[1];
return (roles[email] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR', 'VIEWER']) || (roles[domain] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR', 'VIEWER']);
}
function userCanPublish() {
let roles = getRoles();
let email = request.auth.token.email;
let domain = '*@' + email.split('@')[1];
return (roles[email] in ['ADMIN', 'EDITOR']) || (roles[domain] in ['ADMIN', 'EDITOR']);
}
function userCanEdit() {
let roles = getRoles();
let email = request.auth.token.email;
let domain = '*@' + email.split('@')[1];
return (roles[email] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR']) || (roles[domain] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR']);
}
function userIsAdmin() {
let roles = getRoles();
let email = request.auth.token.email;
let domain = '*@' + email.split('@')[1];
return (roles[email] == 'ADMIN') || (roles[domain] == 'ADMIN');
}
}
}
}
Then, in Firestore, create a doc at Projects/<id> (where <id> is the plugin's id) with a roles map from your email to ADMIN. After that, manage users from the CMS's settings page.
The roles are:
-
ADMIN: everything, including managing users and settings.
-
EDITOR: edit and publish content.
-
CONTRIBUTOR: edit drafts, but not publish.
-
VIEWER: read-only access.
A role can be granted to a whole domain with an entry like *@example.com. Anyone with a verified email on that domain gets the role, so use it sparingly, never with a free email provider, and prefer per-email grants for ADMIN and EDITOR.
Google Drive and Sheets
Some CMS features, like importing from Google Sheets and Drive, use Google APIs on the editor's behalf. To enable them:
-
In the Google Cloud console, enable the Google Sheets API and Google Drive API.
-
Create an OAuth client id (web application) and an API key.
-
Pass them to the plugin as
gapi: {clientId, apiKey}, e.g. from environment variables.
Root AI
Root AI lets editors chat with, draft and translate content in the CMS. Enable it with the ai option, listing the models editors can choose from and their API keys. See the v3 migration guide for an example.
Scheduled jobs
Scheduled publishing, version history and a few other features rely on a job that calls the /cms/api/cron.run endpoint every few minutes.
Firebase: if your site is deployed to Cloud Functions for Firebase, export the cron function from @blinkk/root-cms/functions:
// @/index.ts
import {server} from '@blinkk/root/functions';
import {cron} from '@blinkk/root-cms/functions';
export const www = {
server: server({mode: 'production'}),
cron: cron(),
};
Other hosting: on App Engine, Cloud Run or elsewhere, create a Cloud Scheduler job that calls /cms/api/cron.run on your site every few minutes.
Credentials in production
The server accesses Firestore with application default credentials. On Google Cloud (App Engine, Cloud Run, Cloud Functions), the runtime's service account is used, so give it access to Firestore. Elsewhere, set GOOGLE_APPLICATION_CREDENTIALS to a service account key file.