shovelbasedocs

Functions

Functions are your project's server-side code: Deno programs — standard Deno.serve handlers, deployed as-is — that run on shovelbase instead of on a server you maintain. Each function lives in its own directory with an index.ts entry point; shared code goes in _shared/.

Why use it

Most of a typical app needs no backend code at all: the client talks to /rest/v1 directly and RLS decides what it may see. Functions are for the cases that model can't express — and the test for “do I need one?” is usually one of two questions: does this need a secret? or does this need to be trusted?

Anything shipped to a browser or an app binary is public, so a third-party API key can't live there — it has to run somewhere the user can't read. And a rule the client enforces isn't enforced at all, because the client is under the user's control; “only charge once” or “only admins may refund” has to live on the server. A function is that server-side place, without a server: no container to size, no host to patch, and it scales to zero when nothing calls it.

The trade-off is that a function is a network hop with a cold start. Don't wrap plain CRUD in one “to have an API layer” — that just makes queries slower and adds code to maintain, when PostgREST and RLS already do it.

Calling a third-party API that needs a secret
An LLM, a payment provider, an email service. The key lives in Secrets and never leaves the runtime — the client calls your function, your function calls the vendor.
Business rules that must be trusted
Applying a discount code, awarding credits, moving money. The client asks; the function decides, validates, and writes with the service key.
Reacting to events
A queue message or a cron schedule (below) triggers the same handler — nightly cleanups, digest emails, syncing an external system.
Giving AI clients tools
An MCP endpoint (below) is a function that exposes your data as tools Claude and other MCP clients can call, with your auth rules in front of it.

Writing and deploying one

shovelbase/functions/chat/index.ts
Deno.serve(async (req: Request) => {
const { messages } = await req.json();
const apiKey = Deno.env.get('OPENAI_API_KEY'); // from: shovelbase secrets set
// … call your model, stream a response, etc.
return new Response(JSON.stringify({ ok: true }), {
headers: { 'content-type': 'application/json' },
});
});
terminal
shovelbase functions deploy chat # → https://<ref>.shovelbase.com/functions/v1/chat

jsr:, npm: and URL imports work, as do streaming/SSE responses. Each function has a verify_jwt flag (default on): requests must carry a valid project JWT (the anon key counts). Deploy with --no-verify-jwt for public webhooks. Console output and errors land in Logs → Functions.

SurfaceAvailability
Portal UIRead-only code view with a verify_jwt toggle and delete; separate Schedules, MCP, and Secrets sub-pages
CLIshovelbase functions deploy/list/delete, mcp list, secrets set/list/unset
SDK / HTTPshovelbase.functions.invoke() / POST /functions/v1/<name>
Your appfunctions.invoke() / HTTPSchedulecronQueue webhookportal polls ~10sMCP clientClaude Code, etc.Edge functionDeno.serve handlerDatabase / Storagevia injected keysSecretsenv, from Secrets

Injected environment

VariableValue
SHOVELBASE_URLThe project URL — create a client inside a function to call your own database
SHOVELBASE_ANON_KEYAnon key
SHOVELBASE_SERVICE_ROLE_KEYService key (RLS bypass)
SHOVELBASE_DB_URLDirect Postgres connection string
JWT_SECRET, PROJECT_REF, S3_BUCKETProject JWT secret, ref, storage bucket
(your secrets)Everything from Secrets, below, applied to all functions immediately

SHOVELBASE_* is the canonical prefix; APP_* aliases are also injected.

Schedules

Cron-triggered runs of a project's edge functions — like pg_cron, but for functions. Create a schedule on the portal's Functions → Schedules page or from the CLI: pick the function, a cron expression, and an optional JSON payload it's invoked with. Useful for the recurring jobs a queue-triggered function can't cover — nightly syncs, cleanup, digest emails. They trigger functions the same way a queue trigger does, just on a timer instead of a message. The cron expression is five UTC fields — minute hour day month weekday. No SDK surface; manage schedules on the portal or the CLI.

terminal
shovelbase functions schedules create nightly --function report --cron "0 3 * * *" --payload '{"tz":"utc"}'
shovelbase functions schedules list # cron → function timers
shovelbase functions schedules enable nightly | disable nightly
shovelbase functions schedules delete nightly [--yes]

Secrets

Encrypted values shared by every function in the project — an API key for a third-party service, for example. Manage them on Functions → Secrets or from the CLI; changes apply live, no redeploy needed. No SDK surface — secrets are injected into the function runtime's environment, never exposed to a client.

terminal
shovelbase secrets set OPENAI_API_KEY=sk- # env for ALL functions, applied live
shovelbase secrets list
shovelbase secrets unset OPENAI_API_KEY

MCP endpoints

An MCP endpoint is a function that speaks the Model Context Protocol over Streamable HTTP, so AI clients — Claude Code, Claude Desktop, anything MCP-capable — can call tools backed by your project: query the database, trigger jobs, read storage. Same Deno runtime and deploys as any function; the --mcp flag serves it at https://<ref>.shovelbase.com/mcp/v1/<name> and lists it under the portal's Functions → MCP page.

This is how you expose your product's tools — the ones only your app knows how to provide. To let an AI client manage the project instead — run migrations, deploy this function, set its secrets — you don't write anything: every project already has a management MCP endpoint with a token you create in Settings → API.

terminal
shovelbase functions deploy portfolio --mcp # from ./shovelbase/functions/portfolio/
shovelbase mcp list
# portfolio auth=bearer-key https://<ref>.shovelbase.com/mcp/v1/portfolio

The portal's New MCP endpoint button deploys a working template: a stateless JSON-RPC server handling initialize, tools/list and tools/call, with a TOOLS map you extend — each tool is a name, a description, a JSON schema, and a handler that can use the project's injected env (SHOVELBASE_URL, keys, your secrets) like any function.

terminal
# verify_jwt on (default): clients authenticate with the anon key
claude mcp add --transport http portfolio \
https://<ref>.shovelbase.com/mcp/v1/portfolio \
--header "Authorization: Bearer $ANON_KEY"
# deployed with --no-verify-jwt (endpoint does its own auth): drop the header

HTTP API (/functions/v1)

edge functions — /functions/v1
curl -X POST "https://<ref>.shovelbase.com/functions/v1/chat" \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" -d '{"messages": []}'

See shovelbase-js for shovelbase.functions.invoke().