# How to Extend the Analytics Dashboard with Custom API Endpoints

> Extend your analytics dashboard with custom API endpoints using TypeScript. Learn how to create handlers, utilize CORS, and integrate with Supabase for seamless data management.

- Repository: [Daniel Avila/claude-code-templates](https://github.com/davila7/claude-code-templates)
- Tags: how-to-guide
- Published: 2026-04-26

---

**To extend the analytics dashboard with custom API endpoints, create a TypeScript file under `dashboard/src/pages/api/` that exports named HTTP method handlers such as `POST` or `GET`, leveraging the existing CORS utilities and Supabase client from `davila7/claude-code-templates` to handle requests and persist data.**

The analytics dashboard in the `davila7/claude-code-templates` repository is built with **Astro** and **React**, utilizing Astro's file-based routing for API endpoints. Custom endpoints follow the architectural patterns established in the existing analytics collection, ensuring compatibility with the dashboard's data ingestion pipeline and frontend components.

## Understanding the Dashboard Architecture

The dashboard workspace implements server-side logic through **Astro API routes**. Any file placed under `dashboard/src/pages/api/` automatically becomes a server endpoint, with the route path mirroring the file location. This convention powers existing analytics features such as [`track-download-supabase.ts`](https://github.com/davila7/claude-code-templates/blob/main/track-download-supabase.ts) and [`track-command-usage.ts`](https://github.com/davila7/claude-code-templates/blob/main/track-command-usage.ts), which handle telemetry ingestion for the template ecosystem.

Key architectural decisions to note:

- **File-based routing** eliminates manual route registration
- **Named exports** (`POST`, `GET`, `OPTIONS`) define HTTP method handlers
- **Centralized utilities** in `dashboard/src/lib/api/` handle cross-cutting concerns like CORS and database connections

## Creating a Custom API Endpoint

### File Location and Naming Conventions

Place new endpoint files under `dashboard/src/pages/api/` using descriptive kebab-case names. For example, a metric tracking user engagement might be named [`track-custom-metric.ts`](https://github.com/davila7/claude-code-templates/blob/main/track-custom-metric.ts).

Required structure:

- Path: `dashboard/src/pages/api/[endpoint-name].ts`
- Extension: `.ts` (TypeScript)
- Export pattern: Named exports for HTTP methods

### Implementing the Route Handler

Astro expects handlers to use the `APIRoute` type from `astro`. The standard pattern involves parsing request payloads, validating required fields, interacting with the database via Supabase, and returning JSON responses using the shared `jsonResponse` utility.

```typescript
// dashboard/src/pages/api/track-custom-metric.ts
import type { APIRoute } from 'astro';
import { jsonResponse, corsResponse } from '../../lib/api/cors';
import { supabase } from '../../lib/api/neon';

export const POST: APIRoute = async ({ request }) => {
  // Parse incoming JSON payload
  const payload = await request.json();

  // Validate required fields against your metric schema
  if (!payload?.event || !payload?.userId) {
    return jsonResponse({ error: 'Missing required fields' }, { status: 400 });
  }

  // Insert record into Supabase
  const { error } = await supabase
    .from('custom_metrics')
    .insert({
      event: payload.event,
      user_id: payload.userId,
      metadata: payload.metadata ?? null,
      created_at: new Date().toISOString(),
    });

  if (error) {
    return jsonResponse({ error: error.message }, { status: 500 });
  }

  return jsonResponse({ ok: true });
};

// Expose OPTIONS endpoint for CORS preflight requests
export const OPTIONS = corsResponse;

```

## Reusing Core Utilities

The repository provides standardized helpers to ensure consistency across endpoints.

**CORS Handling**

Import `corsResponse` and `jsonResponse` from [`dashboard/src/lib/api/cors.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/lib/api/cors.ts). This utility applies consistent CORS headers required for client-side calls from the dashboard frontend.

**Database Client**

The Supabase client is instantiated in [`dashboard/src/lib/api/neon.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/lib/api/neon.ts). Import this singleton to execute queries against the analytics database without re-initializing connection pools.

**Error Handling Pattern**

Follow the established pattern seen in [`track-command-usage.ts`](https://github.com/davila7/claude-code-templates/blob/main/track-command-usage.ts): return `jsonResponse` with appropriate HTTP status codes (400 for validation errors, 500 for database failures, 200 for success).

## Integrating with the Frontend

After creating the endpoint, you can surface the data in the dashboard UI.

### Creating Service Functions

Add client-side data fetching logic to [`dashboard/src/lib/collections-api.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/lib/collections-api.ts) or create a dedicated service file. Use standard `fetch` calls to your new endpoint:

```typescript
// dashboard/src/lib/collections-api.ts
export async function fetchCustomMetrics() {
  const response = await fetch('/api/track-custom-metric?type=summary');
  if (!response.ok) throw new Error('Failed to fetch metrics');
  return response.json();
}

```

### Building React Components

Create visualization components in `dashboard/src/components/analytics/` that consume your service functions:

```tsx
// dashboard/src/components/analytics/CustomMetricChart.tsx
import { useEffect, useState } from 'react';
import { Bar } from 'react-chartjs-2';

export const CustomMetricChart = () => {
  const [data, setData] = useState<any>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/track-custom-metric?type=summary')
      .then(res => res.json())
      .then(setData)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <p>Loading metrics...</p>;
  if (!data) return <p>No data available</p>;

  return <Bar data={data} options={{ responsive: true }} />;
};

```

Embed the component in any Astro page:

```tsx
// dashboard/src/pages/analytics.astro
---
import { CustomMetricChart } from '../components/analytics/CustomMetricChart';
---
<Layout title="Analytics">
  <h2>Custom Metric Overview</h2>
  <CustomMetricChart client:load />
</Layout>

```

## Testing and Deployment

Run `npm run dev` from the `dashboard` directory to hot-reload your new endpoint locally. Astro automatically detects files in `src/pages/api/` and maps them to the corresponding URL paths (e.g., [`track-custom-metric.ts`](https://github.com/davila7/claude-code-templates/blob/main/track-custom-metric.ts) becomes `/api/track-custom-metric`).

When ready for production, commit your changes and push to the repository. The **Vercel CI/CD pipeline** defined in [`.github/workflows/deploy.yml`](https://github.com/davila7/claude-code-templates/blob/main/.github/workflows/deploy.yml) automatically redeploys the dashboard with your new endpoints.

## Summary

- Create endpoint files in `dashboard/src/pages/api/` using kebab-case naming
- Export named HTTP method handlers (`POST`, `GET`, `OPTIONS`) using the `APIRoute` type
- Import CORS utilities from [`dashboard/src/lib/api/cors.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/lib/api/cors.ts) to handle cross-origin requests
- Reuse the Supabase client from [`dashboard/src/lib/api/neon.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/lib/api/neon.ts) for database operations
- Astro automatically registers routes based on file paths without explicit configuration
- Deploy through the existing Vercel pipeline by pushing to the repository

## Frequently Asked Questions

### Do I need to manually register new API endpoints in Astro?

No. Astro's file-based routing automatically registers any file under `dashboard/src/pages/api/` as an endpoint. The URL path mirrors the file path, so [`dashboard/src/pages/api/track-custom-metric.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/pages/api/track-custom-metric.ts) becomes available at `/api/track-custom-metric` without additional configuration.

### What database client should I use for custom endpoints?

Use the Supabase client exported from [`dashboard/src/lib/api/neon.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/lib/api/neon.ts). This file instantiates a configured client singleton that reuses connection pools across requests, following the pattern established in [`track-download-supabase.ts`](https://github.com/davila7/claude-code-templates/blob/main/track-download-supabase.ts) and other analytics endpoints.

### How do I handle CORS for cross-origin requests?

Import `corsResponse` and `jsonResponse` from [`dashboard/src/lib/api/cors.ts`](https://github.com/davila7/claude-code-templates/blob/main/dashboard/src/lib/api/cors.ts). Export an `OPTIONS` handler set to `corsResponse` (as shown in the reference implementations) and wrap all JSON responses with the `jsonResponse` utility to apply standard CORS headers automatically.

### Where should I place frontend components that consume the new API?

Create React components in `dashboard/src/components/analytics/` and import them into pages under `dashboard/src/pages/`. Use the `client:load` or `client:visible` directive in Astro files to hydrate interactive components that fetch data from your custom endpoints.