FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
panphora
panphorahyperclay
a single-tenant version of hyperclay
Public
Like
hyperclay
Home
Code
5
ARCHITECTURE.md
DEBUG_GUIDE.md
DEV_GUIDE.md
README.md
H
index.ts
Branches
1
Pull requests
Remixes
1
History
Environment variables
3
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
DEV_GUIDE.md
Code
/
DEV_GUIDE.md
Search
9/5/2025
DEV_GUIDE.md

šŸ› ļø Developer Guide

Understanding the Code Structure

server.js ā”œā”€ā”€ Configuration (lines 1-43) │ ā”œā”€ā”€ Imports (Hono, Val.town services) │ ā”œā”€ā”€ Environment variables │ └── Storage keys │ ā”œā”€ā”€ Core Services (lines 45-162) │ ā”œā”€ā”€ readBlob() - Consistent blob reading │ └── auth object - All authentication logic │ ā”œā”€ā”€ Templates & Helpers (lines 164-358) │ ā”œā”€ā”€ TEMPLATES - All HTML templates │ ā”œā”€ā”€ respond - Response helpers │ └── handleAsync - Error handling wrapper │ ā”œā”€ā”€ Middleware (lines 459-486) │ └── requireAdmin - Protected route auth │ └── Routes (lines 490-651) ā”œā”€ā”€ GET / - Main app ā”œā”€ā”€ Auth endpoints (/auth/*) ā”œā”€ā”€ POST /save/app - Save content ā”œā”€ā”€ GET /versions - History └── GET /reset-dangerous - Reset

Adding Your First Feature

Let's add a simple page counter:

1. Add Counter Display to Template

// Find DEFAULT_HTML (line ~22) const DEFAULT_HTML = `<!DOCTYPE html> <html> <head> <script src="https://hyperclay.com/js/hyperclay-starter-kit.js"></script> </head> <body> <h1>My Notes</h1> <div id="counter">Views: 0</div> <!-- Add this --> <textarea persist edit-mode-input></textarea> </body> </html>`;

2. Create Counter Logic

// Add after auth object (line ~163) const counter = { async increment() { const key = `${APP_NAMESPACE}_counter`; const current = await blob.getJSON(key) || { count: 0 }; current.count++; await blob.setJSON(key, current); return current.count; }, async get() { const key = `${APP_NAMESPACE}_counter`; const data = await blob.getJSON(key) || { count: 0 }; return data.count; } };

3. Update Main Route

// In the GET "/" route (line ~491) app.get("/", async (c) => { const html = await getCurrentHTML(); const count = await counter.increment(); // Add this // Inject count into HTML const updatedHtml = html.replace( 'Views: 0', `Views: ${count}` ); // ... rest of route return c.html(updatedHtml); });

Common Customizations

Change Default Template

// Line ~22 - Modify DEFAULT_HTML const DEFAULT_HTML = ` <!-- Your custom starting template --> `;

Add New Route

// Add after other routes (line ~600+) app.get("/api/stats", requireAdmin, handleAsync(async (c) => { const versions = await getVersionHistory(); return c.json({ versions: versions.length, lastUpdated: new Date() }); }));

Customize Authentication

// Modify token expiry (line ~64) const expiry = Date.now() + (30 * 60 * 1000); // 30 mins instead of 15

Add Webhook Support

// New endpoint for external services app.post("/webhook", handleAsync(async (c) => { const payload = await c.req.json(); // Verify webhook secret const secret = c.req.header('X-Webhook-Secret'); if (secret !== Deno.env.get('WEBHOOK_SECRET')) { return respond.unauthorized(c); } // Process webhook console.log('Webhook received:', payload); return respond.success(c, 'Webhook processed'); }));

Key Files & Functions

ComponentPurposeLine #
STORAGE_KEYSAll storage key patterns37-43
auth objectAuthentication logic60-162
TEMPLATESHTML templates165-286
respondResponse helpers289-331
handleAsyncError handling334-358
requireAdminAuth middleware460-486
RoutesAll endpoints490-651

Environment Variables

VariableRequiredDescription
ADMIN_EMAILāœ… YesYour email for admin access
APP_NAMESPACENoStorage namespace (default: "default")
DEBUGNoEnable debug logs ("true"/"false")

šŸ“š API Reference

Public Endpoints

  • GET / - Main application (shows your HTML)
  • GET /auth/edit - Login page
  • POST /auth/edit - Send magic link
  • GET /auth/verify?token=... - Verify magic link
  • GET /auth/logout - Logout

Admin-Only Endpoints

  • POST /save/app - Save HTML (called by Hyperclay)
  • GET /versions - View version history
  • GET /reset-dangerous - Reset to default template āš ļø

šŸ› Troubleshooting

Can't Sign In?

  1. Check ADMIN_EMAIL matches your email exactly
  2. Check spam folder for magic link
  3. Magic links expire in 15 minutes - try again

Changes Not Saving?

  1. Make sure you're signed in (check for edit controls)
  2. Press Ctrl+S or wait for auto-save
  3. Check browser console for errors

Edit Mode Not Working?

  1. Clear browser cookies and sign in again
  2. Make sure Hyperclay script is loading (check console)
  3. Try a different browser

Storage Limits

  • Free: 10MB total
  • Pro: 1GB total

Debug Mode

Set DEBUG=true in environment variables to see detailed logs in Val.town.

šŸ”— Resources

Documentation

  • Val.town Docs
  • Hono.js
  • Hyperclay
  • Val.town + Hono Guide

Example Projects

  • Personal Site: Portfolio with editable bio and projects
  • Landing Page: Product page with instant updates
  • Documentation: Maintain docs directly in browser
  • Blog: Write posts without a CMS
  • Dashboard: Data visualization with live editing

License

Open source - use freely on Val.town.


Need more details? See valtown.md for advanced Val.town and Hono.js documentation.

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesVersion controlCLIAI agentsCode intelligenceSlack integrationsGTMPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
Ā© 2025 Val Town, Inc.