Skip to main content
All CollectionsClaude for Work (Team & Enterprise Plans)Enterprise Plan Features
Creating Usage Analytics with Claude for Enterprise Audit Logs
Creating Usage Analytics with Claude for Enterprise Audit Logs
Updated over a week ago

Claude for Enterprise provides comprehensive audit logs that enable organizations to gain valuable insights into their Claude usage patterns. This guide will walk you through creating an interactive analytics dashboard using your organization's audit log data.

Prerequisites

  • Access to Claude for Enterprise

  • Primary Owner privileges to access Recorded Events in audit logs (available in CSV or JSON format)

  • A Claude project for analytics

Step-by-Step Implementation

1. Create a Dedicated Analytics Project

Begin by creating a new Claude project specifically for usage analytics:

  1. Navigate to your Claude workspace

  2. Create a new project named "Usage Analytics"

  3. Open the project settings to add custom instructions

2. Configure Project Instructions

Add the custom instructions that are pasted at the end of this document to your Usage Analytics project. These instructions will enable Claude to create consistent, interactive dashboards whenever you upload audit log data.

The dashboard will provide:

Key Metrics Display

  • Total unique users

  • Total conversations

  • Total projects

  • Total uploaded files

Interactive Features

  • Overview tab showing daily active users trend

  • Conversations tab with detailed metrics:

    • Period summaries (7-day, 30-day, all-time)

    • Per-user conversation statistics

    • User engagement leaderboard

    • Daily conversation trends

3. Generate Analytics

Once your project is configured:

  1. Download your organization's audit log in CSV format (Instructions to access audit logs)

  2. Start a new chat in your Usage Analytics project

  3. Upload the audit log CSV file

  4. Ask Claude: "Can you create an interactive dashboard for me using the project custom instructions?"

4. Explore and Customize

The resulting dashboard provides various ways to analyze your organization's Claude usage:

  • Track user adoption through the daily active users chart

  • Monitor conversation volumes across different time periods

  • Identify usage patterns

  • Analyze file and project volumes

You can further customize the dashboard by asking Claude to modify specific aspects or add new visualizations based on your needs.

Custom Instructions for Your Project

Copy and paste the following instructions into your ‘Usage Analytics’ project.

(NOTE: You only need to do this once and then can simply upload your most recent audit log CSV into the chat to have it pull from these custom instructions)

Custom instructions

Our goal is to produce simple analysis and visualization of usage patterns. 
When I upload an audit logs CSV, do NOT use the analysis tool - you don't need it.
Instead, you should create a React data visualization Artifact with
a) Summary Cards showing:
- Total unique users
- Total conversations
- Total projects
- Total files

b) Tab Navigation:
- Overview: Daily active users trend
- Conversations:
* Summary metrics for 7D/30D/All-time periods
* Per-user conversation counts for each period (7D/30D/All-time)
* User conversation leaderboard with last seen dates
* Daily conversations trend

c) Tables & Charts:
- Use standard HTML tables with Tailwind styling
- Use Recharts for visualizations
- Ensure all tables are sortable
- Include last seen dates where relevant

Technical Requirements:
- Use window.fs.readFile for file access
- Handle all JSON parsing errors gracefully
- Focus on conversation metrics as primary KPI

Here's some starter Artifact code that might help:
```tsx
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { Users, MessageSquare, FolderKanban, FileText } from 'lucide-react';
import Papa from 'papaparse';
import _ from 'lodash';
const MetricCard = ({ title, value, icon: Icon }) => (
<div className="bg-white rounded-lg shadow p-4 flex flex-col">
<div className="flex items-center justify-between pb-2">
<h3 className="text-sm font-medium text-gray-600">{title}</h3>
<Icon className="h-4 w-4 text-gray-400" />
</div>
<div className="text-2xl font-bold">{value}</div>
</div>
);
const SortableTable = ({ data, columns }) => {
const [sortConfig, setSortConfig] = useState({ key: '', direction: 'asc' });
const sortedData = React.useMemo(() => {
if (!sortConfig.key) return data;

return [...data].sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === 'asc' ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === 'asc' ? 1 : -1;
}
return 0;
});
}, [data, sortConfig]);
const requestSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}
setSortConfig({ key, direction });
};
return (
<div className="relative overflow-x-auto shadow-md rounded-lg">
<table className="w-full text-sm text-left text-gray-700">
<thead className="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
{columns.map((column) => (
<th
key={column.key}
onClick={() => requestSort(column.key)}
className="px-6 py-3 cursor-pointer hover:bg-gray-100"
>
{column.label}
{sortConfig.key === column.key && (
<span>{sortConfig.direction === 'asc' ? ' ↑' : ' ↓'}</span>
)}
</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map((row, i) => (
<tr key={i} className="bg-white border-b hover:bg-gray-50">
{columns.map((column) => (
<td key={column.key} className="px-6 py-4">
{typeof column.format === 'function'
? column.format(row[column.key])
: row[column.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};
const Dashboard = () => {
const [activeTab, setActiveTab] = useState('overview');
const [data, setData] = useState(null);
const [metrics, setMetrics] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const loadData = async () => {
try {
const response = await window.fs.readFile('audit_logs.csv', { encoding: 'utf8' });
const result = Papa.parse(response, {
header: true,
dynamicTyping: true,
skipEmptyLines: 'greedy'
});
const processedData = result.data.map(row => {
const actorInfo = typeof row.actor_info === 'string' ?
JSON.parse(row.actor_info.replace(/'/g, '"')) : row.actor_info;

return {
...row,
email: actorInfo?.metadata?.email_address || 'Unknown',
userName: actorInfo?.name || 'Unknown',
date: new Date(row.created_at),
dateStr: new Date(row.created_at).toISOString().split('T')[0]
};
});
const userMetrics = {};

processedData.forEach(row => {
if (!userMetrics[row.email]) {
userMetrics[row.email] = {
name: row.userName,
email: row.email,
totalActions: 0,
conversations: 0,
projects: 0,
files: 0,
lastSeen: row.date
};
}
userMetrics[row.email].totalActions++;
if (row.event === 'conversation_created') userMetrics[row.email].conversations++;
if (row.event === 'project_created') userMetrics[row.email].projects++;
if (row.event === 'file_uploaded') userMetrics[row.email].files++;

if (row.date > userMetrics[row.email].lastSeen) {
userMetrics[row.email].lastSeen = row.date;
}
});
const dailyUsers = _.chain(processedData)
.groupBy('dateStr')
.map((rows, date) => ({
date,
activeUsers: _.uniqBy(rows, 'email').length
}))
.orderBy(['date'], ['asc'])
.value();
const dailyConversations = _.chain(processedData)
.filter({ event: 'conversation_created' })
.groupBy('dateStr')
.map((rows, date) => ({
date,
conversations: rows.length
}))
.orderBy(['date'], ['asc'])
.value();
setData(processedData);
setMetrics({
userMetrics: Object.values(userMetrics),
dailyUsers,
dailyConversations,
totalUsers: _.uniqBy(processedData, 'email').length,
totalConversations: processedData.filter(d => d.event === 'conversation_created').length,
totalProjects: processedData.filter(d => d.event === 'project_created').length,
totalFiles: processedData.filter(d => d.event === 'file_uploaded').length,
});

setIsLoading(false);
} catch (error) {
console.error('Error loading data:', error);
setIsLoading(false);
}
};
loadData();
}, []);
if (isLoading || !metrics) {
return <div className="p-4">Loading...</div>;
}
return (
<div className="p-4 max-w-7xl mx-auto">
<h1 className="text-2xl font-bold mb-6">Usage Analytics Dashboard</h1>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<MetricCard
title="Total Users"
value={metrics.totalUsers}
icon={Users}
/>
<MetricCard
title="Total Conversations"
value={metrics.totalConversations}
icon={MessageSquare}
/>
<MetricCard
title="Total Projects"
value={metrics.totalProjects}
icon={FolderKanban}
/>
<MetricCard
title="Total Files"
value={metrics.totalFiles}
icon={FileText}
/>
</div>
<div className="mb-6">
<div className="border-b border-gray-200">
<nav className="flex space-x-4" aria-label="Tabs">
{['overview', 'conversations'].map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`${
activeTab === tab
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
} whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm capitalize`}
>
{tab}
</button>
))}
</nav>
</div>
</div>
<div className="space-y-6">
{activeTab === 'overview' && (
<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-lg font-medium mb-4">Daily Active Users</h3>
<div className="h-72">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={metrics.dailyUsers}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="activeUsers" stroke="#3b82f6" name="Active Users" />
</LineChart>
</ResponsiveContainer>
</div>
</div>
)}
{activeTab === 'conversations' && (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-sm font-medium text-gray-600 mb-2">Last 7 Days</h3>
<div className="text-2xl font-bold">
{data.filter(row =>
row.date >= new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) &&
row.event === 'conversation_created'
).length}
</div>
</div>
<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-sm font-medium text-gray-600 mb-2">Last 30 Days</h3>
<div className="text-2xl font-bold">
{data.filter(row =>
row.date >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) &&
row.event === 'conversation_created'
).length}
</div>
</div>
<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-sm font-medium text-gray-600 mb-2">All Time</h3>
<div className="text-2xl font-bold">
{data.filter(row => row.event === 'conversation_created').length}
</div>
</div>
</div>

<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-lg font-medium mb-4">Conversations by User</h3>
<SortableTable
data={_.chain(metrics.userMetrics)
.map(user => ({
name: user.name,
email: user.email,
conversations7d: data.filter(row =>
row.email === user.email &&
row.date >= new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) &&
row.event === 'conversation_created'
).length,
conversations30d: data.filter(row =>
row.email === user.email &&
row.date >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) &&
row.event === 'conversation_created'
).length,
conversationsTotal: user.conversations,
lastSeen: user.lastSeen
}))
.orderBy(['conversationsTotal'], ['desc'])
.value()}
columns={[
{ key: 'name', label: 'User' },
{ key: 'conversations7d', label: 'Last 7 Days' },
{ key: 'conversations30d', label: 'Last 30 Days' },
{ key: 'conversationsTotal', label: 'All Time' },
{ key: 'lastSeen', label: 'Last Seen', format: (date) => date.toLocaleDateString() }
]}
/>
</div>

<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-lg font-medium mb-4">Daily Conversations</h3>
<div className="h-72">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={metrics.dailyConversations}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="conversations" stroke="#3b82f6" name="Conversations" />
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
)}
}
</div>
</div>
);
};
export default Dashboard;
```

Did this answer your question?