TinaCMS is a Git-backed headless CMS that provides visual editing on top of static sites. User management depends on your deployment model: Tina Cloud (hosted service) manages users through its dashboard, while self-hosted Tina requires configuring your own authentication backend.
How TinaCMS User Management Works
TinaCMS has two distinct deployment modes with different user management approaches:
- Tina Cloud -- Users are managed through the
app.tina.iodashboard. Authentication is handled by Tina's hosted auth service. Users log in via the/adminroute on your site. - Self-Hosted Tina -- You provide your own auth backend. Users are managed through whatever identity provider you configure (Auth0, NextAuth.js, custom JWT, etc.).
In both cases, TinaCMS does not store content in a database -- it commits directly to your Git repository. Users edit content through the visual editor and changes become Git commits.
Adding Users on Tina Cloud
Via the Tina Cloud Dashboard
- Log in to
https://app.tina.io - Select your project from the dashboard
- Click Collaborators in the left sidebar
- Click Invite Collaborator
- Enter the collaborator's email address
- Select their role:
- Owner -- Full access to project settings, billing, and user management
- Admin -- Can manage content, media, and project configuration
- Editor -- Can edit and publish content through the visual editor
- Read Only -- Can view content in the admin but cannot make changes
- Click Send Invite
The invited user receives an email with a link to accept. They will create a Tina Cloud account if they do not already have one.
Managing User Access to Specific Branches
Tina Cloud ties content editing to Git branches. By default, editors work on your main branch. To configure branch-based workflows:
// tina/config.ts
import { defineConfig } from 'tinacms';
export default defineConfig({
branch:
process.env.TINA_BRANCH ||
process.env.VERCEL_GIT_COMMIT_REF ||
process.env.HEAD ||
'main',
clientId: process.env.TINA_CLIENT_ID,
token: process.env.TINA_TOKEN,
build: {
outputFolder: 'admin',
publicFolder: 'public',
},
schema: {
collections: [
// your collections
],
},
});
Editors can switch branches in the admin UI, and their changes commit to the selected branch.
Adding Users on Self-Hosted Tina
NextAuth.js Integration
For self-hosted deployments, NextAuth.js is the most common authentication approach:
// pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GithubProvider from 'next-auth/providers/github';
export default NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
// Validate against your user store
const user = await validateUser(credentials?.email, credentials?.password);
return user || null;
},
}),
],
callbacks: {
async session({ session, token }) {
// Add role information to the session
session.user.role = token.role;
return session;
},
},
});
// tina/config.ts (self-hosted auth configuration)
import { defineConfig, LocalAuthProvider } from 'tinacms';
import { AuthJsBackendAuthProvider } from 'tinacms-authjs';
const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === 'true';
export default defineConfig({
authProvider: isLocal
? new LocalAuthProvider()
: new AuthJsBackendAuthProvider({
authCallbackRoute: '/api/auth/callback',
}),
contentApiUrlOverride: '/api/tina/gql',
build: {
outputFolder: 'admin',
publicFolder: 'public',
},
schema: {
collections: [
// your collections
],
},
});
Managing a User Allow-List
For self-hosted Tina, control access with an email allow-list:
// lib/auth.ts
const AUTHORIZED_EDITORS = [
'admin@company.com',
'editor1@company.com',
'editor2@company.com',
];
export function isAuthorizedEditor(email: string | null | undefined): boolean {
if (!email) return false;
return AUTHORIZED_EDITORS.includes(email.toLowerCase());
}
// Use in NextAuth authorize callback
async authorize(credentials) {
const user = await validateUser(credentials?.email, credentials?.password);
if (user && isAuthorizedEditor(user.email)) {
return user;
}
return null;
}
Environment-Based User Configuration
// Store authorized users in environment variables
// .env
TINA_AUTHORIZED_USERS=admin@company.com,editor1@company.com,editor2@company.com
// lib/auth.ts
const authorizedUsers = (process.env.TINA_AUTHORIZED_USERS || '')
.split(',')
.map(email => email.trim().toLowerCase())
.filter(Boolean);
export function isAuthorizedEditor(email: string): boolean {
return authorizedUsers.includes(email.toLowerCase());
}
Bulk User Management
Tina Cloud Bulk Invitations
Tina Cloud does not offer a bulk invite feature in the dashboard. For teams, invite users sequentially through the Collaborators page.
Self-Hosted Bulk User Setup
For self-hosted deployments with a database-backed auth system:
// scripts/seed-users.ts
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
const users = [
{ email: 'admin@company.com', name: 'Admin', role: 'admin' },
{ email: 'editor1@company.com', name: 'Editor One', role: 'editor' },
{ email: 'editor2@company.com', name: 'Editor Two', role: 'editor' },
{ email: 'writer1@company.com', name: 'Writer One', role: 'writer' },
];
async function seedUsers() {
for (const user of users) {
const tempPassword = Math.random().toString(36).slice(-10);
const hashedPassword = await bcrypt.hash(tempPassword, 10);
await prisma.user.upsert({
where: { email: user.email },
update: {},
create: {
email: user.email,
name: user.name,
role: user.role,
password: hashedPassword,
},
});
console.log(`Created: ${user.email} (temp password: ${tempPassword})`);
}
}
seedUsers()
.then(() => prisma.$disconnect())
.catch(console.error);
Removing and Deactivating Users
Removing Collaborators from Tina Cloud
- Log in to
https://app.tina.io - Select your project
- Click Collaborators
- Find the user in the list
- Click the Remove button next to their name
- Confirm removal
The user immediately loses access to the project's admin interface. Their Tina Cloud account remains active for other projects.
Self-Hosted User Removal
Remove from the allow-list:
// Update TINA_AUTHORIZED_USERS in your environment
// Remove the email from the comma-separated list
// Redeploy your application
Database-backed removal:
// Deactivate (recommended)
await prisma.user.update({
where: { email: 'departing@company.com' },
data: { isActive: false },
});
// Or permanently delete
await prisma.user.delete({
where: { email: 'departing@company.com' },
});
What Happens to Their Content
TinaCMS content lives in Git, not in a database. When a user is removed:
- All content they created or edited remains in the Git repository unchanged
- Git commit history preserves their name and email as the commit author
- No content is lost or orphaned because it exists as files in the repository
- Media uploads (images, files) remain in the repository's media directory
- Draft content on unpublished branches persists until the branches are deleted
This is a significant advantage of Git-backed CMS systems -- content is never tied to user accounts.
Revoking Git Access
If users had direct Git repository access (separate from TinaCMS), also revoke that:
# GitHub: remove collaborator from repo
gh api repos/org/repo/collaborators/username -X DELETE
# GitLab: remove member from project
# Navigate to Settings > Members > Remove member
SSO and Enterprise Authentication
Tina Cloud SSO
Tina Cloud supports GitHub and Google social login by default. For enterprise SSO:
- Contact Tina's enterprise team for SAML/OIDC support on Tina Cloud
- Tina Cloud's auth layer handles identity; your site delegates to it
Self-Hosted SSO
Self-hosted Tina supports any authentication provider compatible with your framework:
// NextAuth.js with Azure AD (enterprise SSO)
import AzureADProvider from 'next-auth/providers/azure-ad';
export default NextAuth({
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID!,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,
tenantId: process.env.AZURE_AD_TENANT_ID!,
}),
],
callbacks: {
async signIn({ user, account, profile }) {
// Only allow users from your organization
return user.email?.endsWith('@company.com') ?? false;
},
async session({ session, token }) {
// Map Azure AD groups to TinaCMS roles
session.user.role = token.groups?.includes('CMS-Editors')
? 'editor'
: 'viewer';
return session;
},
},
});
Access Audit Checklist
- Review Tina Cloud collaborators quarterly (or your self-hosted user list)
- Audit Git commit history to verify all committers are authorized:
git log --format='%ae' | sort -u - Check that removed collaborators no longer have GitHub/GitLab repository access
- Review API tokens and client IDs in your Tina configuration
- Verify NextAuth session duration and refresh settings for self-hosted deployments
- Check that branch protection rules prevent unauthorized direct pushes
- Audit environment variables for any hardcoded credentials that should be rotated
- Document all collaborator changes in your project's access log