RepoAssets is a self-hostable asset management dashboard that stores files and metadata in your own GitHub repository. It is built for small image/audio libraries, prototypes, student projects, indie games, public sample datasets, and personal media collections.
Users get a friendly browser UI for uploading, browsing, filtering, editing, and deleting assets. GitHub remains the source of truth: files are committed through the GitHub Contents API, and metadata is stored in a JSON manifest.
- Uploads images, audio, and other files to configurable GitHub folders.
- Stores asset metadata in a configurable JSON manifest such as
data/assets.json. - Supports configurable fields through
dam.config.json. - Provides search, type filters, collection filters, tag filters, and audio filters.
- Supports metadata edits, file replacement, linked audio replacement/removal, and asset deletion.
- Falls back to a read-only demo dataset when the API is offline.
- React 19 + Vite
- TypeScript
- Express 5
- GitHub Contents API via Octokit
- Multer for multipart file uploads
.
+-- src/ # React dashboard
+-- server/ # Express API and maintenance scripts
+-- public/demo-assets.json # Read-only demo data
+-- docs/
+-- dam.config.json # Local dashboard/schema config
+-- dam.config.example.json
+-- .env.example
+-- package.json
- Node.js 20 or newer recommended.
- A GitHub personal access token with permission to write to the target asset repository.
- A GitHub repository that will store uploaded files and the manifest.
Install dependencies:
npm installCreate your local environment file:
cp .env.example .envFill in the GitHub repository settings:
GITHUB_TOKEN=ghp_exampletoken
GITHUB_OWNER=owner-name
GITHUB_REPO=repoassets-library
GITHUB_BRANCH=main
MANIFEST_PATH=data/assets.json
IMAGE_DIR=images
AUDIO_DIR=audio
FILE_DIR=files
PORT=4000
ALLOWED_ORIGINS=http://localhost:5173
MAX_FILE_SIZE_BYTES=15728640Keep .env private. It contains the token used to commit files to GitHub.
Edit dam.config.json to change the dashboard name, manifest path, upload folders, and fields.
{
"projectName": "My Asset Library",
"projectDescription": "Images, audio, and metadata stored in GitHub.",
"manifestPath": "data/assets.json",
"assetFolders": {
"images": "images",
"audio": "audio",
"files": "files"
},
"fields": [
{ "name": "title", "label": "Title", "type": "text", "required": true },
{ "name": "description", "label": "Description", "type": "textarea" },
{ "name": "collection", "label": "Collection", "type": "text" },
{ "name": "tags", "label": "Tags", "type": "tags" },
{
"name": "license",
"label": "License",
"type": "select",
"options": ["personal", "internal", "public", "unknown"]
}
]
}Supported field types:
texttextareaselecttagsurl
Run the frontend and API server in separate terminals:
npm run devnpm run serverBy default:
- Dashboard:
http://localhost:5173 - API:
http://localhost:4000 - Health check:
http://localhost:4000/health
If the API is hosted somewhere else, set VITE_API_BASE_URL for the frontend.
npm run dev # Start the Vite dashboard
npm run server # Start the Express API
npm run build # Type-check and build the frontend
npm run lint # Run ESLint
npm run preview # Preview the production build
npm run migrate:manifest # Legacy Sanskrit manifest migration helper
npm run migrate:manifest:apply
npm run upload:bulk # Legacy Sanskrit bulk upload helperManifest entries are stored as an array:
{
"id": "hero-banner",
"title": "Hero Banner",
"description": "Homepage campaign image",
"type": "image",
"tags": ["marketing", "homepage"],
"collection": "Website",
"files": {
"original": "https://raw.githubusercontent.com/owner/repo/main/images/hero-banner.png",
"audio": ""
},
"metadata": {
"license": "internal",
"altText": "Product dashboard screenshot"
},
"source": {
"fileName": "hero-banner.png",
"mimeType": "image/png",
"size": 128000
},
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}Returns the active dashboard configuration.
Returns assets from the manifest.
Optional query params:
q: searches title, description, tags, collection, type, and metadatatype:image,audio, orfilecollection: exact collection matchtag: exact tag matchhasAudio:trueorfalse
Accepts multipart/form-data and creates a new asset.
Fields:
originalFile- requiredaudioFile- optional linked audio fileassetId- optional slug override- configured fields from
dam.config.json
Updates an asset's metadata. It can also replace the primary file, replace linked audio, or remove linked audio with removeAudio=true.
Deletes the manifest entry and removes associated GitHub files when they can be resolved.
The frontend can show public/demo-assets.json when the API is offline. This is useful for a safe read-only public demo because no GitHub token is exposed and visitors cannot write to the sample repository.
For a full writable demo, host the Express API somewhere like Render, Railway, or Fly.io and add authentication before exposing it publicly.
- Asset URLs are written as
raw.githubusercontent.comlinks to reduce cache delay after updates. - GitHub tokens must stay on the server and should never be exposed to the frontend.
- RepoAssets is intended for lightweight libraries, not unlimited storage or high-traffic media delivery.