
<em>A headless CMS for Supabase Storage. Built with Next.js 14, TypeScript, and Tailwind CSS.</em>
<a href="https://github.com/StructuredLabs/supawald/raw/v1.0.2/LICENSE">
<img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="Apache 2.0 License">
</a>
<a href="https://nodejs.org/">
<img src="https://img.shields.io/badge/node-18%2B-blue.svg" alt="Node.js Version">
</a>
<a href="https://nextjs.org/">
<img src="https://img.shields.io/badge/Next.js-14-black" alt="Next.js">
</a>
<a href="https://supabase.com/">
<img src="https://img.shields.io/badge/Supabase-Platform-green" alt="Supabase">
</a>
A headless CMS for Supabase Storage. Built with Next.js 14, TypeScript, and Tailwind CSS. Provides a clean interface for managing files in Supabase Storage buckets with real-time updates and static site generation support.

Supawald is a file management system that turns Supabase Storage into a full-featured CMS. It's designed for developers who need a simple way to manage assets for their Next.js applications, blogs, or any project using Supabase Storage.
Real-time updates via Supabase subscriptions
Developer Experience
Publish API for static site generation
Storage Features
![]() |
![]() |
Quick access to frequently used assets
Document Management
Secure access control via bucket policies
Application Assets
npx create-supawald my-app
cd my-app
npm install
npm run dev
git clone https://github.com/yourusername/supawald.git
cd supawald/template
npm install
npm run dev
Set Up Supabase
sql
-- Create a new bucket in Supabase Storage
-- Name it something like 'blog-content' or 'assets'
-- Set appropriate privacy settings (public/private)
Configure Environment
bash
cp .env.example .env.local
```env
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_BUCKET_NAME=your-bucket-name
# Auth (for admin access) AUTH_USERNAME=admin AUTH_PASSWORD=your-secure-password
# Publish API (for static site generation) PUBLISH_URL=https://your-site.com/api/publish PUBLISH_TOKEN=your-secure-token ```
bash
npm run devSupawald includes a publish API that triggers regeneration of static pages that depend on Supabase Storage data. This is useful for: - Blog posts that display uploaded images - Product pages with product images - Any static page that needs to reflect changes in your storage bucket
pages/api/publish.ts:```typescript import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// Check for secret to confirm this is a valid request
if (req.headers.authorization !== Bearer ${process.env.PUBLISH_TOKEN}) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
// Revalidate your static pages
await res.revalidate('/') // Revalidate homepage
await res.revalidate('/blog') // Revalidate blog pages
await res.revalidate('/products') // Revalidate product pages
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
} ```
env
PUBLISH_URL=https://your-static-site.com/api/publish
PUBLISH_TOKEN=your-secure-tokenIn your static site:
env
PUBLISH_TOKEN=your-secure-token # Same token as above

// In your static site's page component
export async function getStaticProps() {
// Fetch data from Supabase Storage
const { data: images } = await supabase.storage
.from('your-bucket')
.list('blog-images')
return {
props: {
images,
// ... other props
},
// Revalidate every hour
revalidate: 3600
}
}
When you update an image in Supawald and click publish: 1. The image is updated in Supabase Storage 2. The publish API is called 3. Your static pages are regenerated with the new image 4. The changes are live on your static site
// Example: Upload a file
const { data, error } = await supabase.storage
.from('your-bucket')
.upload('path/to/file.jpg', file)
// Example: Get file URL
const { data: { publicUrl } } = supabase.storage
.from('your-bucket')
.getPublicUrl('path/to/file.jpg')
// Trigger static page regeneration
await fetch('/api/publish', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PUBLISH_TOKEN}`
}
})
Use signed URLs for temporary access
Authentication
Use HTTPS in production
Environment Variables
.env.localPUBLISH_TOKEN secure and only share with trusted services# Install dependencies
npm install
# Run development server
npm run dev
# Build for production
npm run build
# Start production server
npm start
See CONTRIBUTING.md for development guidelines.
Apache 2.0 - See LICENSE for details.
<a href="https://www.linkedin.com/company/structuredlabs/" target="_blank">
<img src="https://img.shields.io/badge/Follow%20Us-LinkedIn-blue?style=for-the-badge&logo=linkedin" alt="Follow us on LinkedIn">
</a>
<a href="https://x.com/StructuredLabs" target="_blank">
<img src="https://img.shields.io/badge/Follow%20Us-Twitter-1DA1F2?style=for-the-badge&logo=twitter" alt="Follow us on Twitter">
</a>
$ claude mcp add supawald \
-- python -m otcore.mcp_server <graph>