Claude Code for Val Town — Workflow Guide
The Setup
You are building serverless functions, scheduled jobs, and HTTP endpoints using Val Town — a platform where each function (called a “val”) is independently deployable and shareable. Claude Code can write vals for APIs, cron jobs, and email handlers, but it treats Val Town like a traditional Node.js project instead of respecting its unique module system and runtime constraints.
What Claude Code Gets Wrong By Default
-
Creates project structure with package.json. Claude scaffolds a full Node.js project with dependencies. Val Town vals are single files that import from URLs or the
@stdlibrary. There is nonode_modulesor build step. -
Uses CommonJS require statements. Claude writes
const express = require('express'). Val Town runs pure ESM with Deno-style URL imports. Useimportsyntax withnpm:specifiers orhttps://URLs. -
Writes Express/Fastify route handlers. Claude generates full server frameworks for HTTP endpoints. Val Town HTTP vals export a default function that receives a
Requestand returns aResponse— the Web API standard, no framework needed. -
Ignores Val Town’s built-in blob and SQLite storage. Claude reaches for external databases. Val Town provides
@std/blobfor key-value storage and@std/sqlitefor SQL queries built into the platform at no extra cost.
The CLAUDE.md Configuration
# Val Town Project
## Runtime
- Platform: Val Town (Deno-based serverless)
- Modules: ESM only, import via npm: specifier or URL
- Storage: @std/blob (key-value), @std/sqlite (SQL)
- HTTP: Export default function(req: Request): Response
## Val Town Rules
- Each val is a single file — no multi-file projects
- HTTP vals: export default async function(req: Request)
- Cron vals: export default async function(interval: Interval)
- Email vals: export default async function(email: Email)
- Import npm packages with "npm:package-name" prefix
- Use Deno.env.get("KEY") for environment variables
- Store persistent data with @std/blob or @std/sqlite
- No file system access — use blob storage instead
## Conventions
- Val names are camelCase: myApiEndpoint, dailyReport
- Keep vals under 200 lines — split logic into helper vals
- Use console.email() for notifications from cron vals
- Reference other vals with @username/valName imports
- Never hardcode secrets — use Val Town environment variables
Workflow Example
You want to create a webhook endpoint that stores GitHub star events. Prompt Claude Code:
“Create a Val Town HTTP val that receives GitHub webhook POST requests for star events, stores the stargazer username and timestamp in SQLite, and returns the total star count as JSON.”
Claude Code should produce a single val file that exports a default async function, parses the webhook payload, uses @std/sqlite to insert the event and query the count, and returns a new Response(JSON.stringify({...})) with the total.
Common Pitfalls
-
Using
fetchfor val-to-val communication. Claude calls other vals via HTTP fetch. For vals you own, import them directly withimport { helper } from "@username/helperVal"— it is faster and does not count as an HTTP request. -
SQLite schema drift. Claude creates tables in the val body, running CREATE TABLE on every invocation. Use
CREATE TABLE IF NOT EXISTSor check for table existence first, since vals can be invoked thousands of times. -
Blob storage size limits. Claude stores large datasets in
@std/blob. Each blob value has a size limit and there are total storage limits per account. For larger data, use an external database and store only the connection config in environment variables.