# Val Town Full Documentation
# Val Town Docs
URL: https://docs.val.town/index.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
Val Town is a collaborative website to create and scale JavaScript functions. Create APIs, crons, store data – all from the browser, and deployed in miliseconds.
##### Where to start
If you're new to Val Town, we recommend starting with [Townie](https://www.val.town/townie), our AI assistant that can write vals for you.
We also recommend our quickstarts.
##### Val Town Standard Library
Our standard library provides free hosted services.
##### API and SDK
You can access Val Town programmatically via our [REST API](/api/) or [JavaScript SDK](/api/sdk).
##### Contact us
We're here to help! If you have any questions or feedback, please reach out to us on [Discord](https://discord.gg/dHv45uN5RY) or [email](mailto:docs-help@val.town).
---
# Contact Us
URL: https://docs.val.town/contact-us/contact-us.md
We are always happy to hear from you! If you have any questions, feedback, or just want to say hi, feel free to reach out to us using any of the methods below.
- [hi@val.town](mailto:hi@val.town)
- [Sign up for our email newsletter](https://newsletter.val.town/)
- [Join our Discord](https://discord.gg/dHv45uN5RY)
---
# Overview
URL: https://docs.val.town/api.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
import { Aside } from "@astrojs/starlight/components";
Val Town's REST API allows you to programmatically manage your account and its resources: vals, blob storage, sqlite databases, likes, comments, and more.
---
# Authentication
URL: https://docs.val.town/api/authentication.md
Val Town's REST API supports Bearer Token authentication.
You can create and manage your API tokens on the [API Tokens page](https://www.val.town/settings/api).
If you're using the Val Town API from within Val Town, a short-lived API token is automatically injected into your environment variables. These injected tokens are what provide authenticated access to Val Town Standard Library services.
All tokens are scoped to the permissions you've granted them.
## Scopes
Val Town API tokens are scoped to read/write scopes for:
- `val` – [vals](https://docs.val.town/openapi#tag/vals)
- `user` – [user account details](https://docs.val.town/openapi#tag/me)
- `blob` – [blob storage](https://docs.val.town/openapi#tag/blobs)
- `sqlite` – [sqlite database](https://docs.val.town/openapi#tag/sqlite)
- `email` – [ability to send emails](https://docs.val.town/openapi#tag/emails)
You can confiure the scopes on the [API Tokens page](https://www.val.town/settings/api) or the settings page of your val.
The default scope for vals exclue `val:write` and `user:write`
to limit potential damage from
misconfiguration, vulnerable libraries, or account compromises. You can
manually enable those scopes if you need them, but we advise extreme
caution when doing so. Be sure to audit all your dependencies recursively
for such vals.
## API Token Lifecycles
API Tokens come with configurable expiration dates. We strongly recommend
setting expiration dates for your tokens and rotating them regularly.
If you accidentally leak or misplace an API token, you can delete it [on the API
Tokens page](https://www.val.town/settings/api), immediately preventing any
further access to your account from that token.
In rare cases where we detect a potential leak of your API Token, we'll take
proactive measures. We'll automatically revoke the compromised token and
promptly notify you via email.
---
# JavaScript SDK
URL: https://docs.val.town/api/sdk.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
The Val Town TypeScript SDK lets you interact with our
[REST API](/api/overview) from the comfort of a typed
client that works well with editor autocomplete.
The Val Town SDK runs in:
- Val Town
- Node.js 18 LTS+
- Deno v1.28.0+
- Bun 1.0+
- Cloudflare Workers
- Vercel Edge Runtime
### Getting started in Val Town
The quickest way to get started with the SDK is to use it in
Val Town, for example by [forking this val](https://www.val.town/v/stevekrouse/demoSDK).
```ts title="Using the SDK in Val Town" val
import ValTown from "npm:@valtown/sdk";
const vt = new ValTown();
// print your username
const me = await vt.me.profile.retrieve();
console.log(me.email);
// list some of your vals
const { data: vals } = await vt.users.vals.list(me.id, {});
console.log(vals);
// list vals you've liked
const { data: likes } = await vt.me.likes.list({});
console.log(likes);
```
Authentication is set by the `VAL_TOWN_API_KEY` environment
variable, which is automatically set within Val Town. You can control the
API scopes of that key in your val's settings page.
### Getting started in Node.js
Here is how to get started with the SDK,
with Node.js, writing [ESM](https://nodejs.org/api/esm.html).
You should have [Node.js](https://nodejs.org/en) already
installed, version 18 or newer.
There are _many_ ways to set up JavaScript and TypeScript
projects, and it's likely that you already have a project
you intend to integrate against, so we don't document every
approach.
```bash title="Setting up an example Node.js project"
# Create a directory for your project
mkdir example-project
cd example-project
# Create a package.json file
npm init
# Install the SDK
npm install @valtown/sdk
```
Create a file named `index.mjs`. Note that it needs to end
with `.mjs`, not `.js`, because this file is using ESM import
syntax. Alternatively, you can [add `"type": "module"` to your package.json file.](https://nodejs.org/api/esm.html#enabling)
```ts title="index.mjs"
import ValTown from "@valtown/sdk";
const valTown = new ValTown();
async function main() {
const myProfile = await valTown.me.profile.retrieve();
console.log(myProfile);
}
main();
```
Finally, the API expects to be authenticated with an API
token, so [create an API token on Val Town](https://www.val.town/settings/api), and set it in your
terminal environment:
```bash title="Setting your Val Town API Token"
export VAL_TOWN_API_KEY=your api token…
```
Now you should be able to run `index.mjs` and get your profile
information:
```bash title="Running index.mjs and getting profile information"
node index.mjs
{
id: '19892fed-baf3-41fb-a5cc-96c80e95edec',
bio: '👷 Building Val Town',
username: 'tmcw',
profileImageUrl: 'https://img.clerk.com/eyJ0eXBl…',
tier: 'pro',
email: 'tom@macwright.com'
}
```
---
# Vulnerability Disclosure Policy
URL: https://docs.val.town/contact-us/security.md
We appreciate investigative work into our system's security by ethical security researchers. If you discover a vulnerability, please contact us so we can take steps to address it. We offer bug bounties as compensation, depending on the severity of the exploit found.
## Reporting a vulnerability
Please email findings to [security@val.town](mailto:security@val.town).
## Responsible Disclosure
- Do not take advantage of the vulnerability or problem you have discovered. For
example only download data that is necessary to demonstrate the
vulnerability - do not download any more. Do not delete, modify, or view
other people's data.
- Do not publish or reveal the problem until it has been resolved.
- Do not use attacks on physical security, social engineering, distributed
denial of service, spam or applications of third parties.
## Our commitment
- If you act in accordance with this policy, we will not take legal action
against you in regard to your report.
- We will handle your report with strict confidentiality, and not pass on your
personal details to third parties without your permission.
- We offer bug bounties as compensation, depending on the severity of
the exploit found.
## In-scope domains
Val Town uses the following domains:
- `val.town`
- `valtown.email`
- `api.val.town`
- `esm.town`
- `val.run`
Subdomains of these domains are in scope for the program.
## Out of scope issues
- Reports that target vulnerabilities on outdated or deprecated browsers, open source libraries, or infrastructure
- Reports relating to missing security hardening headers
- Reports from automated tools or scans
- Our policies on presence/absence of SPF/DMARC/DKIM/CAA/BIMI records
- Self-XSS or developer console code execution
- Login/logout CSRF
- Phishing or social engineering attacks
- Brute force login attempts
- Bugs on Vals themselves. Vals are user-controlled code and are not part of Val Town's product surface
- Denial of Service or brute force attacks
- Violating any laws or breaching any agreements in order to discover vulnerabilities
---
# Creating a webhook
URL: https://docs.val.town/guides/creating-a-webhook.md
import Val from "@components/Val.astro";
Webhooks are for realtime server-to-server communication. Many apps – like GitHub,
Discord, and Stripe – let you configure webhooks so that you can get programmatic notifications.
When the event happens, they will send an HTTP request to a URL you specify.
For example, we registered
[this val](https://www.val.town/v/stevekrouse/newStripeEvent) to receive webhooks from Stripe
when we get a new subscriber to Val Town Pro. It sends new subscribers a thank you email and notifies our team in our internal Discord channel.
## Creating a webhook
While the specifics vary by service, the general steps are:
1. Create an HTTP handler to receive the webhook
2. Register the webhook with the service
3. Handle the incoming webhook
4. Secure the webhook by validating the request came from the service
Here's a guide for [receiving a webhook from Github](/integrations/github/receiving-a-github-webhook).
---
# Generate PDFs
URL: https://docs.val.town/guides/generate-pdfs.md
import Val from "@components/Val.astro";
You can generate PDFs using an external library like [jsPDF](https://github.com/parallax/jsPDF).
Here's a more comprehensive example that builds an invoice.

---
# Embedding Vals in other sites
URL: https://docs.val.town/guides/embed.md
import Val from "@components/Val.astro";
Vals are made to be shared, and not just on Val Town! Public vals can
embedded using iframes on other websites.
## Embedding an existing val
You can embed any public or unlisted val on another site by copying the embed
URL from the val menu:

### Embedding in WYSIWYG editors
When writing for the web, lots of people use WYSIWYG editors, such as those in
Wordpress or Notion. In tools with a simple writing experience, you can paste
your embed URL straight in. Here's how it looks when pasted into Notion:
In other editors, you might have to create an "embed" and paste the URL into the
form that the tool provides.
### Embedding in HTML
For sites that don't provide an editing experience as smooth as Notion or
Wordpress, you can embed your val using an
[iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
element.
Here's an example, using a web page created by a val that embeds itself:
And here's the final web page:
[https://neverstew-embedself.web.val.run/](https://neverstew-embedself.web.val.run/)
## Embedding a new val template
In some scenarios, especially those where you're providing a guide or a post
that the reader is following along with, it's useful to provide a template for
your readers to fork. You can do that using the
[https://val.town/embed/new](https://val.town/embed/new) endpoint. To generate a
new val:
1. visit [https://val.town/embed/new](https://val.town/embed/new)
2. type the code that you want to appear
3. copy the new URL of the page you are on
Here's an example:
The URL contains the fragment of code that populates the embed. For example, for
the embed above, the URL you would use is
[https://www.val.town/embed/new?code=const+myNewEmbeddedVal+%3D+`The+time+this+ran+was+%24{new+Date().toLocaleTimeString()}`%3B](https://www.val.town/embed/new?code=const+myNewEmbeddedVal+%3D+%60The+time+this+ran+was+%24%7Bnew+Date%28%29.toLocaleTimeString%28%29%7D%60%3B)
---
# Push notifications
URL: https://docs.val.town/guides/push-notifications.md
import Val from "@components/Val.astro";
While we have tentative plans to create `console.push` that would be as easy to
use as `console.email` and might work via
[Web Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API), in
the meantime we recommend [ntfy.sh](http://ntfy.sh) as an easy way to send
yourself push notifications to your phone.
## [ntfy.sh](https://ntfy.sh)
1. Download the app
2. Create an account
3. Add your channel to your
[Val Town Environment Variables](https://val.town/settings/environment-variables)
4. Use `@axelav.sendNotification` to send yourself a message
## Pushover
For iOS, [Pushover](https://pushover.net/) is another great way to get push
notifications.
---
# QR Code
URL: https://docs.val.town/guides/qr-code.md
import Val from "@components/Val.astro";
Generate a QR code for any link, instantly!
Visit
[https://neverstew-generateQR.web.val.run?url=https://neverstew.com](https://neverstew-generateqr.web.val.run/?url=https://neverstew.com)
and replace the url query parameter with your link.
---
# RSS
URL: https://docs.val.town/guides/rss.md
import Val from "@components/Val.astro";
Val Town can both parse and generate [RSS](https://en.wikipedia.org/wiki/RSS) feeds for blogs and other updated sources.
## Polling RSS
You would run this example in val town as a [Cron val](/types/cron)
that ran on a regular interval, and it would check a blog's feed every 15 minutes.
```ts title="Polling example" val
import { email } from "https://esm.town/v/std/email?v=9";
import { newRSSItems } from "https://esm.town/v/stevekrouse/newRSSItems";
import { rssFeeds } from "https://esm.town/v/stevekrouse/rssFeeds";
export async function pollRSSFeeds({ lastRunAt }: Interval) {
return Promise.all(
Object.entries(rssFeeds).map(async ([name, url]) => {
let items = await newRSSItems({
url,
lastRunAt,
});
if (items.length)
await email({
text: JSON.stringify(items, null, 2),
subject: `New from ${name} RSS`,
});
return { name, items };
}),
);
}
```
## Creating RSS
---
# Save HTML form data
URL: https://docs.val.town/guides/save-html-form-data.md
import Val from "@components/Val.astro";
You can submit forms to Val Town using the [HTTP val](/types/http). You can
place these forms on any page on the internet - or host the form directly on Val
Town.
These examples show how to accept and store email addresses on Val Town. The
email addresses are saved into a val, and you also get sent an email
notification for each new signup.
## Add a form to your website
### Create an [HTTP val](/types/http)
Write a val function that accepts a
[Request](https://developer.mozilla.org/en-US/docs/web/api/request) and returns
a [Response](https://developer.mozilla.org/en-US/docs/web/api/response).
### Add the form to your webpage
Copy your val's Web endpoint URL using the menu (Endpoints > Copy web endpoint)
and set it as the form's action (this tells the form where to send its data when
it's submitted).
Below is a full HTML page example. If you are adding a form to an existing page
just copy and paste the `
` block.
```html
Email Form
```
## Host your form on Val Town
There are two ways to do this. You can write a val function that serves a
webpage, and a separate val that accepts the form data - or you can write a
single val that does both like the example below.
When a form is submitted, it sends a HTTP request with the
[POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) method.
When a user visits a webpage in their web browser, the server (your val
function) gets sent a
[GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) request.
You can check the HTTP method using `req.method` and change how your val
function responds.
{/*TODO: When we fix the scroll-to-line issue on embedded vals add "#L5-27" back to the end of this link.*/}
See [Web forms — Working with user data](https://developer.mozilla.org/en-US/docs/Learn/Forms)
on the MDN Web Docs site for more help with forms. Forms are a basic part of
the web - you don't need a lot of front-end JavaScript to make them work.
---
# Saving data from a web page
URL: https://docs.val.town/guides/saving-data-from-a-web-page.md
import Val from "@components/Val.astro";
There are many ways to save data from a web page, depending on your use-case, technologies, and other constraints. First, you'll want to pick a storage mechanism. Val Town has two built-in:
[Blob storage](/std/blob) is a very simple key-value store for storing files, JSON, images, large blobs of data, while [SQLite](/std/sqlite) is a SQL database with ACID transactions, indexes, and more. You can also use external services like Firebase, Supabase, Neon, Upstash, etc.
Once you've picked a storage mechanism, you'll need to decide how to interact with it. You can either use form submissions or client-side JavaScript to make API calls to Val Town.
## Blob storage
For example, this val is an app that displays comments, accepts new comments, and allows you to delete comments.
There are three parts relating to blob storage:
1. `GET /` - Getting the comments from blob storage to render them on the page
2. `POST /comment` - Handling the form submission to add a new comment to blob storage
3. `POST /comment/:id/delete` – Handling the delete button to remove a comment from blob storage
The way this val is rendered (Hono JSX) and the the HTML page talks to the server (form submissions) is arbitrary and can be replaced with any other front-end framework or server-side rendering. For example, you could have a NextJS or Remix app that makes a call to Val Town to get the data. Or you a client side VueJS app that makes a call to Val Town to get the data from the browser.
This is a fork of the above app that uses client-side React to make the API calls:
You could easily host the React part anywhere (Vercel, Netlify, etc) and have it make calls to Val Town (via your val's HTTP URL) to get and modify the data.
You can learn more about blob storage here: [Val Town Blob](/std/blob).
## SQLite example
The above demo is simple because it stores the data in the same simple JSON format that you want on the frontend. However, there are many limitations to blob storage. The most egregious is that it's not concurrency safe. If two people try to add a comment at the same time, one of the comments will be lost. For that – and many other reasons, such as indexing, querying, etc – you might want to use SQLite.
This is the same Hono demo but using SQLite on the backend. We will leave it as an exercise to an ambitious and helpful reader to convert the React demo to use SQLite and submit a PR to this docs page.
Note: we left the `CREATE TABLE` statement in the code, so that this val will immediately work if you fork it. However, you may want to comment out that line after the first run to speed up this val.
You can learn more about SQLite here: [Val Town SQLite](/std/sqlite).
---
# Website Uptime Tracker
URL: https://docs.val.town/guides/website-uptime-tracker.md
import Val from "@components/Val.astro";
Examples from the community:
---
# Web scraping
URL: https://docs.val.town/guides/web-scraping.md
You can use vals to scrape websites, either by fetching HTML and using a parsing library, or by making an API call to
an external service that runs a headless browser for you.
## Locate the HTML element that contains the data you need
Right click on the section of a website that contains the data you want to fetch
and then inspect the element. In Chrome, the option is called **Inspect** and it
highlights the HTML element in the Chrome DevTools panel.
For example, to scrape the introduction paragraph of the
[OpenAI](https://en.wikipedia.org/wiki/OpenAI) page on Wikipedia, inspect the
first word of the first paragraph.

In the **Elements** tab, look for the the data you need and right click the
parent element and choose **Copy selector** to get the
[CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors):
`#mw-content-text > div.mw-parser-output > p:nth-child(7)`.

If you know a little CSS, it's better to write your own selector by hand to make
it more generic. This can make the selector less vulnerable to the website being
updated and, e.g., class names changing. In this case, you can use
`p:nth-of-type(2)`.
Optionally, use `document.querySelector` in the **Console** to check your CSS
selector is correct.

## Parsing HTML
The [OpenAI](https://en.wikipedia.org/wiki/OpenAI) page on Wikipedia is rendered
on the server and arrives as a complete HTML document. This makes it a good fit
for [cheerio](https://www.val.town/examples/packages/cheerio) which parses HTML
markup and provides an API for traversing/manipulating the resulting data
structure. We also recommend
[node-html-parser](https://www.val.town/examples/packages/node-html-parser) and
[linkedom](https://www.val.town/search?q=linkedom).
```ts title="Example" val
import { fetchText } from "https://esm.town/v/stevekrouse/fetchText?v=6";
import { load } from "npm:cheerio";
const html = await fetchText(
"https://en.wikipedia.org/wiki/OpenAI",
);
const $ = load(html);
// Cheerio accepts a CSS selector, here we pick the second
const intro = $("p:nth-of-type(2)").first().text();
console.log(intro);
```
```txt title="Logs"
OpenAI is an American artificial intelligence (AI) research organization consisting of the non-profit OpenAI, Inc.[5] registered in Delaware and its for-profit subsidiary OpenAI Global, LLC.[6] One of the leading organizations of the AI Spring,[7][8][9] OpenAI researches artificial intelligence with the declared intention of developing "safe and beneficial" artificial general intelligence, which it defines as "highly autonomous systems that outperform humans at most economically valuable work".[10] OpenAI has developed several large language models, advanced image generation models, and previously, also open-source models.[11][12]
```
## API call to external services
See the [Hosted Puppeteer](/guides/hosted-puppeteer-browserless/) guide for using [Browserless](https://www.browserless.io/). Also check out [Browserbase](https://www.browserbase.com/) examples: [browserbase](https://www.val.town/v/stevekrouse/browserbase) and [browserbaseUtils](https://www.val.town/v/charlypoly/browserbaseUtils).
---
# Weather
URL: https://docs.val.town/guides/weather.md
import Val from "@components/Val.astro";
## wttr.in
Val Town is a great system for keeping track of the weather -
for remembering [when to carry an umbrella](https://www.val.town/v/stevekrouse/umbrellaReminder)
and much more. Here's an example of using [wttr.in](https://wttr.in/) to get weather
data.
## Weather data sources
There are many sources for weather data, but some of them require
payment and API keys. If you need something free and you're primarily
interested in weather in the United States, you can check out
the [NOAA weather API](https://www.weather.gov/documentation/services-web-api).
Also, [OpenWeatherMap](https://openweathermap.org/price) provides a decent
number of calls per day for free.
wttr.in also uses [WorldWeatherOnline](https://www.worldweatheronline.com/weather-api/api/docs/local-city-town-weather-api.aspx) as its source
for forecasts and current conditions.
---
# Airtable
URL: https://docs.val.town/integrations/airtable.md
Airtable is a spreadsheet-database hybrid. Note that because
[Val Town](https://val.town) uses Deno, we use an unofficial library to
access Airtable.
## Setup
On the homepage, click Start from scratch to create a base:

If prompted with automatic setup, click Skip. You should get a new base with a
blank table.

Adjust the table fields as you want (note that this isn't covered by this
guide).
Click on your profile icon, then on Developer hub:

Click Create new token.

Fill out the fields:
- Name: Any. For example, `valtown`.
- Scopes: You'll most likely want to add `data.records:read` and
`data.records:write`.
- Access: Add the base you created earlier.
Lastly, click Create token. Here's an example of how your settings could
probably look like:

Copy your token and click Done.

Go to [val.town](https://val.town), click on your username and then on **Env Variables**:

Click **New env variable**. Set the key to a name you want to use to reference the token
(for example, `airtable_pat`) and paste the copied personal access token into
the value, then click **Add**.

You can then use Airtable from [val.town](https://val.town) like so:
```ts title="Example" val
import { Airtable } from "https://deno.land/x/airtable@v1.1.1/mod.ts";
const airtable = new Airtable({
apiKey: Deno.env.get("airtable_pat"),
baseId: "appXSrKDlwbAijRmD",
tableName: "All content",
});
// Sample data from: https://blog.airtable.com/database-vs-spreadsheet/
const results = await airtable.select();
console.log(results);
```
Make sure to change:
- apiKey if you have used a different name for your environment variable.
- baseId to the ID of your base. You can find it in the URL bar while a table is
open and it will likely start with `app`:

- tableName to either the name of your table or the table ID which can also be
found in the URL after the base ID (see above).
---
# Browserbase
URL: https://docs.val.town/integrations/browserbase.md
import Val from "@components/Val.astro";
[Browserbase](https://www.browserbase.com/) offers a reliable, high performance serverless developer platform to run, manage, and monitor headless browsers at scale to power web automation and AI agents.
## Quick start
The quickest way to get started is to get your Steel API key and fork this val.
1. Get your free Browserbase API key at https://www.browserbase.com/
2. Add it to your [Val Town Environment Variables](https://www.val.town/settings/environment-variables) as `BROWSERBASE_API_KEY`
3. Fork [this val](https://www.val.town/v/browserbase/browserbasePuppeteerExample)
4. Click `Run` on that val
5. View the output in the logs
:::note
To use Puppeteer in Val Town, we reccomend using the
[deno-puppeteer](https://deno.land/x/puppeteer@16.2.0) library – as is done in the above starter val.
:::
---
# Browserless
URL: https://docs.val.town/integrations/browserless.md
Some websites are partially (or entirely) rendered on the client (aka your web
browser). If you try to search the initial HTML for elements that haven't
finished rendering, you won't find them.
One solution is to use a headless browser that runs a web browser in the
background that fetches the page, renders it, and _then_ allows you to search
the final document.
Services like [Browserless](https://browserless.io/)
provide APIs to interact with a hosted headless browser. For example, their
[/scrape API](https://www.browserless.io/docs/scrape). Here's how to use
Browserless and Val Town to load a webpage.
## Sign up to Browserless and grab your API Key
Copy your API Key from
[https://cloud.browserless.io/account/](https://cloud.browserless.io/account/)
and save it as a Val Town environment variable as `browserless`.

## Make an API call to the [/scrape API](https://www.browserless.io/docs/scrape)
Check the documentation for the
[/scrape API](https://www.browserless.io/docs/scrape) and form your request.
For example, here's how you scrape the introduction paragraph of OpenAI's
wikipedia page.
```ts title="Scrape API example" val
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
const res = await fetchJSON(
`https://chrome.browserless.io/scrape?token=${Deno.env.get("browserless")}`,
{
method: "POST",
body: JSON.stringify({
url: "https://en.wikipedia.org/wiki/OpenAI",
elements: [
{
// The second
element on the page
selector: "p:nth-of-type(2)",
},
],
}),
}
);
// For this request, Browserless returns one data item
const data = res.data;
// That contains a single element
const elements = res.data[0].results;
// That we want to turn into its innerText value
const intro = elements[0].text;
return intro;
```
Browserless also has more [APIs](https://www.browserless.io/docs/start) for
taking screenshots and PDFs of websites.
## Alternatively, use Puppeteer and a browser running on Browserless
You can use the [Puppeteer](https://pptr.dev/) library to connect to a browser
instance running on Browserless.
Once you've navigated to a page, you can run arbitrary JavaScript with
`page.evaluate` - like getting the text from a paragraph.
```ts title="Puppeteer example" val
import { PuppeteerDeno } from "https://deno.land/x/puppeteer@16.2.0/src/deno/Puppeteer.ts";
const puppeteer = new PuppeteerDeno({
productName: "chrome",
});
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://chrome.browserless.io?token=${Deno.env.get("browserless")}`,
});
const page = await browser.newPage();
await page.goto("https://en.wikipedia.org/wiki/OpenAI");
const intro = await page.evaluate(
`document.querySelector('p:nth-of-type(2)').innerText`
);
await browser.close();
console.log(intro);
```
```txt title="Logs"
"OpenAI is an American artificial intelligence (AI) research laboratory consisting of the non-profit OpenAI Incorporated and its for-profit subsidiary corporation OpenAI Limited Partnership. OpenAI conducts AI research with the declared intention of promoting and developing friendly AI."
```
---
# Google Sheets
URL: https://docs.val.town/integrations/google-sheets.md
You can send data to Google Sheets from a val. You can collect
data from incoming HTTP requests or from an HTTP endpoint on a schedule.
You have two options for authenticating with the Google Sheets API:
1. [Use Pipedream's Accounts API](#use-pipedreams-accounts-api) to fetch a fresh OAuth access token at runtime.
2. [Use your own Google Cloud service account](#use-your-own-google-cloud-service-account).
## Use Pipedream's Accounts API
[Pipedream](https://pipedream.com) provides a workflow automation product for developers. Workflows are built as a sequence of steps — pre-built actions for thousands of APIs or custom code — triggered by an event (HTTP request, timer, when a new row is added to a Google Sheets, and more).
Pipedream also manages the OAuth token refresh process for apps like Google Sheets, exposing a fresh access token in the workflow runtime and the [Accounts API](https://pipedream.com/docs/rest-api#accounts). If you connect your Google Sheets account to Pipedream, you can use the Accounts API to fetch a fresh token within your val and use that token to authenticate requests to the Google Sheets API.
### 1. Sign up for Pipedream and retrieve your API key
[Sign up for Pipedream](https://pipedream.com/auth/signup) and visit [https://pipedream.com/settings/user](https://pipedream.com/settings/user). Find the **API Key** section and copy your key:

You'll use this key to authenticate requests to the Accounts API, letting you fetch Google Sheets access tokens in your val.
### 2. Add that key as a Val Town environment variable
Add the API key as an [environment variable](https://www.val.town/settings/environment-variables) in Val Town. In this example, we've named the environment variable `pipedream_api_key`:

### 3. Connect your Google Sheets account in Pipedream
Visit [https://pipedream.com/accounts](https://pipedream.com/accounts). Click the **Connect an app** button at the top-right:

You'll be directed through the OAuth authorization process to connect your Google Sheets account to Pipedream. Once you've connected your account, you'll see it listed on the Accounts page.
### 4. Copy the account ID
Click the `…` to the right of the account and **Copy Account ID**:

All account IDs are prefixed with `apn_`, so yours should look something like `apn_abc123`.
### 5. Note your sheet ID
In the Google Sheet you'd like to access, copy the sheet ID from the URL bar. It's the long string of characters between `/d/` and `/edit`.

### 6. Fetch a Google Sheets access token and use it in your val
In your val, fetch a fresh Google Sheets access token from Pipedream and use it to authenticate requests:
```ts title="Example" val
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
const accountID = "";
const sheetID = "";
const baseURL = `https://sheets.googleapis.com/v4/spreadsheets/${sheetID}/values`;
async function fetchAccessToken() {
const response = await fetch(
`https://api.pipedream.com/v1/accounts/${accountID}?include_credentials=1`,
{
headers: {
Authorization: `Bearer ${Deno.env.get("pipedream_api_key")}`,
},
},
);
if (!response.ok) {
throw new Error(`Error fetching access token: ${response.statusText}`);
}
const { data } = await response.json();
return data.credentials.oauth_access_token;
}
async function makeSheetsRequest(url, method, body = null) {
const accessToken = await fetchAccessToken();
const options: RequestInit = {
method,
headers: {
Authorization: `Bearer ${accessToken}`,
},
};
if (body) {
options.body = JSON.stringify(body);
}
return fetchJSON(url, options);
}
// Write data to a sheet
await makeSheetsRequest(
`${baseURL}/A1:C1:append?valueInputOption=RAW`,
"POST",
{ values: [[Date(), Math.random(), 1]] },
);
// Read data from a sheet
const data = await makeSheetsRequest(
`${baseURL}/A1:C1?majorDimension=ROWS`,
"GET",
);
console.log(data.values[0]);
```
## Use your own Google Cloud service account
Authenticating with the Google Sheets API is a bit tricky, but we walk you through it below. It should only take a few minutes, and only needs to be done once.
Google recommends [OAuth2 server to server authentication](https://developers.google.com/identity/protocols/oauth2/service-account) for bot-based applications. We will walk you through creating a Google Cloud service account, giving it access to one of your Google Sheets, and using its key to make authenticated requests to the Google Sheets API.
### 1. Create a Google Cloud service account
[Creating a Google Cloud service account](https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount) is the recommended way to authenticate to the Google Sheets API.
#### a. Create a Google Cloud project
Open the [Google Cloud console](https://console.cloud.google.com/) and click on
the **Select a project** dropdown in the top left.

In the dialog that pops up, click on the **New project** button.

Choose any name for the project name, then click **Create**.

#### b. Enable the Google Sheets API
Open the [Google Sheets API page](https://console.cloud.google.com/apis/library/sheets.googleapis.com),
then double check if the correct project is selected.

Once you have made sure that you are using the correct project, click **Enable**.

#### c. Create a service account
On the left of the screen, click on **Credentials**.

On the bar at the top, click on the **Create credentials** button, then select
**Service account**.

Enter any name for the **Service account name**. You may also enter a description,
as you see fit.

Save the email address for later, as it will be required to add the service account
to a Google Sheet.
Click **Done**, as granting permissions to this service account is not required.
#### d. Create a service account key
To the right of the screen, open the menu on the newly created service account,
and click **Manage keys**.

Click **Add key**, then **Create new key**.

Click **Create key**. Leave the type as JSON.

You will get a JSON file in your downloads directory.
#### e. Import the key into Val Town
Open the service account key JSON file in a text editor, and copy the full contents.
Open the [Environment Variables](https://www.val.town/settings/environment-variables) page,
then click **New env variable**.
Set the key to a name like `google_service_account`, then paste the entire JSON data into the value.
Once you are finished, click **Add**.

### 2. Create a sheet and grant access
Open [Google Sheets](https://docs.google.com/spreadsheets/u/0/) and create a new empty sheet or open an existing one.
Click the Share button, then paste your service account's email into the dialog.
Make sure it is added as an editor, and optionally disable "Notify people".

Lastly, copy the sheet ID from the URL bar. It's the long string of characters between `/d/` and `/edit`.

### 3. Create a val to send data
To interact with the Google Sheets API, use the [@mattx.gsheet_call](https://www.val.town/v/mattx/gsheet_call) wrapper.
This automates requesting an access token from Google to access the Google Sheets API.
It requires 4 arguments:
- The contents of your service account JSON file: in almost all cases, this should be retrieved from environment variables
using - for example, `Deno.env.get("google_service_account")`.
- The sheet ID
- The action to perform: This parameter is the part of the URL that comes after `https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/`,
along with any URL parameters that might be required (often this is `valueInputOption`).
For example, for [spreadsheets.values.append](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append),
this will be `values/{range}:append?valueInputOption=RAW`, where `{range}` needs to be substituted for a range like `A1:C3`. You can find a list of available actions in the [Google Sheets API reference](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values).
- The request body: In the same example as above, the request body could be\
`{values: [[1, 2, 3]]}`.
Notice that this is an array of arrays, [in line with the API documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values#ValueRange).
Here is an example of what the above looks like when put together:
```ts title="Example" val
import { gsheet_call } from "https://esm.town/v/mattx/gsheet_call";
// Appending to a sheet
await gsheet_call(
Deno.env.get("google_sa"),
"1LDgOhO6Fxg2wt5rGFH29t6XYbmKe6fXI7fLSFaqZkDA",
"POST",
"values/A1:C1:append?valueInputOption=RAW",
{ values: [[Date(), Math.random(), 1]] },
);
// Reading from the top of the sheet
const data = await gsheet_call(
Deno.env.get("google_sa"),
"1LDgOhO6Fxg2wt5rGFH29t6XYbmKe6fXI7fLSFaqZkDA",
"GET",
"values/A1:C1?majorDimension=ROWS",
{},
);
console.log(data["values"][0]);
```

---
# Steel
URL: https://docs.val.town/integrations/steel.md
import Val from "@components/Val.astro";
[Steel](https://steel.dev/) is an open-source browser API that lets you control fleets of browsers in the cloud.
## Quick start
The quickest way to get started is to get your Steel API key and fork this val.
1. Get your free Steel API key at https://app.steel.dev/settings/api-keys
2. Add it to your [Val Town Environment Variables](https://www.val.town/settings/environment-variables) as `STEEL_API_KEY`
3. Fork [this val](https://www.val.town/v/stevekrouse/steel_puppeteer_starter)
4. Click `Run` on that val
5. View the output in the logs
:::note
To use Puppeteer in Val Town, we recommend using the
[deno-puppeteer](https://deno.land/x/puppeteer@16.2.0) library – as is done in the above starter val.
:::
---
# SQLite wasm
URL: https://docs.val.town/integrations/sqlite-wasm.md
:::caution[Deprecated]
New vals [should use `std/sqlite` instead](/std/sqlite), which is a private SQLite database that comes with every Val Town account.
:::
import Val from "@components/Val.astro";
You can create, insert, query, and persist a whole SQLite database in Val Town via wasm!
## Import & Create Table & Insert
## Example Usage
## Database in Val Town
---
# Stripe
URL: https://docs.val.town/integrations/stripe.md
import Val from "@components/Val.astro";
import { LinkButton } from "@astrojs/starlight/components";
Stripe allows you to accept credit card payments online.
There are a two parts to a payment flow:
1. **Link to Stripe** – User clicks on a link in your site that takes them to a Stripe checkout page.
2. **Handle Fulfillment** – You can have a **webhook** that listens for successful payment events from Stripe. You can use this webhook to update your database to authorize the user to view premium content or send a confirmation email to the user.
## 1. Link to Stripe
This guide covers the two simplest ways to link your users to Stripe:
- [Stripe Payment Link](#stripe-payment-link) – the simplest way to link to Stripe
- [Stripe Checkout](#stripe-checkout) – a more customizable way to link to Stripe
### Stripe Payment Link
Stripe Payment Links are the simplest way to link to Stripe.
Docs: Stripe Payment Links
Create a Stripe Payment Link
Stripe Payment links do not require a backend. Link to them from HTML:
```html
Buy
```
When your users click on the link, they will be taken to a Stripe checkout page where they can enter their credit card information.
### Stripe Checkout
Stripe Checkout is a more customizable way to link to Stripe. You first create a Checkout Session on your server, then redirect the user to that session's URL.
Docs: Stripe Checkout
We ported [Stripe Checkout Quickstart](https://docs.stripe.com/checkout/quickstart) to Val Town: https://val.town/v/std/stripeCheckoutQuickstart
```ts
// @ts-ignore
import { Stripe } from "npm:stripe";
const stripe = new Stripe(Deno.env.get("STRIPE_TEST_API_KEY"));
export default async function (req: Request): Promise {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response(
`
`, {
headers: { "content-type": "text/html" },
});
}
}
```
## 2. Handle Fulfillment
After a user pays, you may need to fulfill their order:
- If you are selling a digital product, this might mean sending them a download link.
- If you are selling a physical product, this might mean shipping the product to them.
- If you are selling a subscription, this might mean updating your database to authorize the user to view premium content.
You can listen for events from Stripe to know when a payment has been successful. You can do this by setting up a **webhook**. A webhook is a URL on your server that Stripe will send events to.
Docs: Stripe Webhooks
Val Town makes it very easy to create a webhook on an HTTP val. Take a look at the example below to get started.
### Example Val Town Stripe Webhook
This webhook listens for new subscribers to Val Town Pro, and sends the Val Town team a Discord notification. The val itself (link below) explains how to set it up.
https://www.val.town/v/stevekrouse/newStripeSubscriber
```ts
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import Stripe from "npm:stripe";
const stripe = new Stripe(
Deno.env.get("stripe_sk_customer_readonly") as string,
{
apiVersion: "2020-08-27",
}
);
function getStripeCustomer(customerId: string) {
return stripe.customers.retrieve(customerId);
}
export let newStripeEvent = async (req: Request) => {
const url = new URL(req.url);
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const signature = req.headers.get("Stripe-Signature");
const body = await req.text();
const webhookSecret = Deno.env.get("STRIPE_WEBHOOK_SECRET");
let event;
try {
event = await stripe.webhooks.constructEventAsync(
body,
signature,
webhookSecret
);
} catch (err: any) {
return new Response(`Webhook Error: ${err.message}`, { status: 400 });
}
console.log(event);
const newSubscription =
event.data.previous_attributes.status === "incomplete" &&
event.data.object.status === "active";
if (!newSubscription) {
console.log("Not a new subscription");
console.log(
event.data.previous_attributes.status,
event.data.object.status
);
return Response.json("ok");
}
const customer = await getStripeCustomer(event.data.object.customer);
const customerEmail = customer.email;
console.log("New subscription", customerEmail);
discordWebhook({
url: Deno.env.get("discordUserEvents"),
content: `New Subscription 🥳 ${customerEmail}`,
});
return Response.json("ok");
};
```
---
# Telegram bot
URL: https://docs.val.town/integrations/telegram.md
import Val from "@components/Val.astro";
This guide shows how to create a Telegram bot that receives and responds to messages on Val Town.
### Introduction
Telegram is a free, privacy-focused messaging app with clients for iOS, Android, Desktop, and Web.
In order to make a Telegram Bot in Val Town, there are three steps:
1. Create your bot
2. Create your webhook handler on Val Town to receive messages
3. Register your webhook handler with Telegram
This guide uses the [grammY Telegram Bot Framework](https://grammy.dev/) , because it has excellent TypeScript support. If you'd prefer to not use any framework, you can refer to [an older version of this guide](https://github.com/val-town/val-town-docs/blob/77fb871c4a9c87fe4a901552636c20c5af2328a4/src/content/docs/integrations/telegram.mdx), which makes the HTTP calls to telegram more directly with simpler helper function.
### 1. Create your bot
Telegram has the best bot-making experience of any messaging platform. You create a bot by talking to a bot! Appropriately, he's called @BotFather.
Start a conversation with @BotFather by clicking this link: [https://t.me/botfather](https://t.me/botfather)
You will need to sign up/in to Telegram if you haven't already.
Type `/newbot` to create a new bot. You will be asked to give your bot a name and a username as shown below:

Be sure to note down the `token` that @BotFather gives you. You will need it in the next step.
### 2. Create a webhook handler to receive messages
Fork this Val Town Project to set up your webhook handler: [@std/telegramBotStarter](https://www.val.town/x/std/telegramBotStarter)
Go to [your Project's environment variables](https://www.val.town/x/me/telegramBotStarter). Add the token from the previous step as `TELEGRAM_TOKEN`.
This is the code you will see in the starter:
```ts title="index.ts" val
import {
Bot,
webhookCallback,
} from "https://deno.land/x/grammy@v1.35.0/mod.ts";
if (!Deno.env.get("TELEGRAM_TOKEN")) {
throw new Error("TELEGRAM_TOKEN is not set");
}
const bot = new Bot(Deno.env.get("TELEGRAM_TOKEN")!);
// Handle the /start command.
bot.command("start", (ctx) => ctx.reply("Welcome! Up and running."));
// Handle other messages.
bot.on("message", (ctx) => ctx.reply("I got " + ctx.message.text));
// Use part of the TELEGRAM_TOKEN itself as the secret_token
// to authorize webhook requests.
const SECRET_TOKEN = Deno.env.get("TELEGRAM_TOKEN")!.split(":")[1];
const handleUpdate = webhookCallback(
bot,
"std/http",
undefined,
undefined,
SECRET_TOKEN
);
let isEndpointSet = false;
export default async (req: Request): Promise => {
// Set webhook if it is not set yet
// Do this in the HTTP handler so we get the endpoint url from req.url
// This is a no-op if nothing's changed
if (!isEndpointSet) {
await bot.api.setWebhook(req.url, {
secret_token: SECRET_TOKEN,
});
isEndpointSet = true;
}
if (req.method === "POST") {
return await handleUpdate(req);
}
return new Response("TODO GET", { status: 200 });
};
```
### 3. Register your webhook handler with Telegram
You may notice in the code above that the webhook handler takes care of registering itself with Telegram. If everything worked properly, your webhook should be registered already and this step is already done 🎉
You can confirm it's set by using this [Telegram Webhook Manager](https://telegram.tools/webhook-manager) or by simply sending a message to your bot.
Once your webhook is registered, you can optionally comment out or delete the lines of code the that register it in the webhook handler.
### Send a message to your bot to check it works!
It should echo back the message like this:

Not working? Get help on [Val Town's Discord](https://discord.gg/dHv45uN5RY).
---
# Branches
URL: https://docs.val.town/projects/branches.md
Every project has a `main` branch, which is the default branch. You can create branches off the `main` branch for feature development, testing, and sharing.
## Creating a branch
You need to be the owner of a project to create a branch. If you are not the owner, you can [remix the project](/projects/remixes) and send a pull request.
You can branch off `main` or any other branch.
## Merging
You can merge changes from a branch back into its parent branch or from the parent into the branch.
### Merging into parent branch
A branch can only be merged into its parent if there are no conflicts. All edits are copied to the parent project, including file and folder renames, additions, deletions, and relocations.
### Merging from parent branch
You can pull updates from the parent project into your branch.
### Merge conflicts
Merges cannot proceed if there are merge conflicts. Merge conflicts happen when the same line has been edited in both the parent and the branch.
You can resolve conflicts by:
1. Merging from the parent into the branch
2. Finding all merge conflicts in the project and resolving them. They will look like:
```
<<<<<<< main
This is the main branch
=======
This is the branch
>>>>>>> branch
```
3. Once all merge conflicts are resolved, you can then merge the branch back into the parent.
### Limitations
- Parent-child relationships are fixed. You can't change the base branch or re-parent a branch.
- There is no support for squashing or rebasing changes.
- You cannot delete a branch currently, but we will add that soon.
- You cannot change the name of the `main` branch.
---
# AWS S3
URL: https://docs.val.town/integrations/s3.md
You can upload and download from AWS S3 inside val functions.
In this guide, you'll create an AWS bucket, an IAM user, and then test your set
up is correct by uploading and downloading a small text file.
## Create an S3 bucket
Log in to the AWS Console and go to
[https://s3.console.aws.amazon.com/s3/bucket/create](https://s3.console.aws.amazon.com/s3/bucket/create)
Create a new bucket by choosing a **Bucket name** (leave the defaults for
everything else).

Save the **Bucket name** and your AWS region as
[Val Town environment variables](https://www.val.town/settings/environment-variables) as `awsS3Bucket` and
`awsS3Region` respectively. Although these values aren't _secrets_, doing this
means you can copy and paste the val examples in this guide without making any
code changes.
Continue to use this AWS region for the rest of this guide.
## Create an IAM user
Go to
[https://console.aws.amazon.com/iamv2/home#/users](https://console.aws.amazon.com/iamv2/home#/users)
and click **Add users**.

Choose a **User name**.

Select **Attach policies directly**.

Give this user `AmazonS3FullAccess` by searching and selecting the checkbox
under **Permissions policies**.
(This allows this user the highest level of S3 permissions across all of the
buckets for the AWS account you're logged into. Consider setting up granular
permissions with AWS's
[Bucket owner granting its users bucket permissions](https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-walkthroughs-managing-access-example1.html)
guide.)

On the final page, click **Create user**.
## Create access keys for the user
Navigate to **IAM** → **Users** → **\**.

In the **Security credentials** tab, click **Create access key**.

In **Step 1**, select **Application running outside AWS**.

In **Step 2**, give your access keys a helpful **Description tag value**.

Finally, copy and save the **Access key** and **Secret access key** as
[Val Town environment variables](https://www.val.town/settings/environment-variables) as `awsS3Key` and
`awsS3Secret` respectively.

## Upload text file
Copy and run the following val in your workspace.
```ts title="Upload example" val
import { S3Client } from "https://deno.land/x/s3_lite_client@0.6.1/mod.ts";
const s3client = new S3Client({
endPoint: `s3.${Deno.env.get("awsS3Region")}.amazonaws.com`,
region: Deno.env.get("awsS3Region"),
bucket: Deno.env.get("awsS3Bucket"),
accessKey: Deno.env.get("awsS3Key"),
secretKey: Deno.env.get("awsS3Secret"),
});
await s3client.putObject("filename.txt", "File contents");
```
[putObject](https://deno.land/x/s3_lite_client@0.6.1/mod.ts?s=S3Client&p=prototype.putObject)
takes any of `ReadableStream | Uint8Array | string`.
## Download the text file you just uploaded
Copy and run the following val in your workspace.
```ts title="Download example" val
import { S3Client } from "https://deno.land/x/s3_lite_client@0.6.1/mod.ts";
const s3client = new S3Client({
endPoint: `s3.${Deno.env.get("awsS3Region")}.amazonaws.com`,
region: Deno.env.get("awsS3Region"),
bucket: Deno.env.get("awsS3Bucket"),
accessKey: Deno.env.get("awsS3Key"),
secretKey: Deno.env.get("awsS3Secret"),
});
const res = await s3client.getObject("filename.txt");
console.log(await res.text());
```
[getObject](https://deno.land/x/s3_lite_client@0.6.1/mod.ts?s=S3Client&p=prototype.getObject)
returns a standard HTTP **Response** object which you consumed with
`.text()`, `.json()`, `.body` (ReadableStream), `.arrayBuffer()`, or `.blob()`.
## More resources
Documentation for the lite S3 client used in this guide can be found here:
[https://deno.land/x/s3_lite_client@0.6.1](https://deno.land/x/s3_lite_client@0.6.1/mod.ts?s=S3Client).
There's also
[S3's documentation](https://docs.aws.amazon.com/s3/index.html?nc2=h_ql_doc_s3).
For all other Val Town help, see [our Discord](https://discord.gg/dHv45uN5RY)!
---
# Overview
URL: https://docs.val.town/projects.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
Projects are groups of related vals, files, and folders that are versioned together. They are used to manage large codebases, organize related files, and collaborate with others.
Learn more about [why we created Val Town Projects in our announcement post](http://blog.val.town/blog/projects/).
## Project features
- **Versioned Workspace:** Projects contain and track changes to vals, files, folders, READMEs via an version number that goes up on any change to any file
- **Multiple Entrypoints:** Add multiple [HTTP](../vals/http), [Cron](../vals/cron), [Email](../vals/email), and [Script](../vals/script) vals
- **Relative Imports:** Import files and folders relative to the project root (ie. `import foo from '../bar/foo'`)
- **Static Files:** Add static files (HTML, CSS, Markdown, etc) to projects
- **Scoped [Environment Variables](../reference/environment-variables)**
## Collaboration
## Limitations
- Maximum files per project: 1000
- Maximum branches per project: 100
- Binary files are not supported. We recommend [Blob Storage](/std/blob).
- Free tier users are limited to 5 private or unlisted projects. Pro users can have unlimited private projects. All users can have unlimited public projects.
Please contact us if you need more resources or have any questions.
---
# Pull Requests
URL: https://docs.val.town/projects/pull-requests.md
Pull Requests are used to merge changes from a branch or remix back into the parent. They are a way to propose changes to a project, and they are the primary way to collaborate on projects with other users.
You will see the ability to make a pull request by visiting the parent project's **Pull requests** page.
---
# Remixes
URL: https://docs.val.town/projects/remixes.md
:::note
**Remixes are Val Town Projects’ version of Forks**.
Forks exist for vals and work similarly to Remixes for Projects, just without the extra collaboration features.
:::
Remixes are a way to create a copy of a Project in your account while maintaining references to the original. They can be used for cross-account collaboration. Changes in a remix can be merged back via [pull requests](../pull-requests).
### Remixing
Remixing creates a copy of a project in your account while maintaining references to the original. This process includes the main branch even when a non-main branch is remixed to maintain consistency.
### Merging
A remix can only be merged into its parent if there are no conflicts. All edits are copied to the parent project, and the version is updated to match the latest version in the parent.
### Limitations
You cannot have two projects with the same name in the same account. This means that you cannot remix a project if you already have a project of that name. It also means that you can't remix a project twice, and that you can't remix your own project. We are working on fixing this.
---
# Your first cron val
URL: https://docs.val.town/quickstarts/first-cron.md
This is a step-by-step guide to a cron val that sends you a weather email every morning.
If you get stuck, please ask for help in the [Val Town Discord](https://discord.gg/dHv45uN5RY). We're friendly!
### Step 1: Sign up to Val Town
[Sign up to Val Town](https://www.val.town/auth/signup). It's free.
:::note
The email you sign up with will be the one that receives the weather emails.
:::
### Step 2: Create a Cron val
In the top-right corner of Val Town, click **+** > **Val** > **Cron**. Give it a name.
This will create and schedule a new empty val in your account that looks like this:
```ts title="Step 2" val
export default async function (interval: Interval) {
// your code…
}
```
This function will be run on a schedule. By default, this runs every hour. You can change the frequency by clicking the clock icon in the top right corner.
### Step 3: Get the weather
In the following example code, we are using `getWeather` from [stevekrouse/getWeather](https://val.town/v/stevekrouse/getWeather). It uses the free [wttr.in](https://wttr.in/:help) service to get weather data.
1. Add these lines to your code
```ts title="Step 3" ins={1,4-5} val
import { getWeather } from "https://esm.town/v/stevekrouse/getWeather";
export default async function (interval: Interval) {
let weather = await getWeather("Brooklyn, NY");
console.log(weather.current_condition[0].FeelsLikeF);
}
```
2. Replace `Brooklyn, NY` with your location
3. Click **Run**
4. View the output in the val's logs
:::tip
You'll notice that we're using a val in another val. This is a common pattern in Val Town. If you type `@` in the val editor, you'll be able to search and import other vals inline.
:::
### Step 4: Send yourself an email
We're going to use [std/email](https://val.town/v/std/email) to send an email to yourself.
1. Add these lines to your code
```ts title="Step 4" ins={1,7-10}
import { email } from "https://esm.town/v/std/email";
import { getWeather } from "https://esm.town/v/stevekrouse/getWeather";
export default async function (interval: Interval) {
let weather = await getWeather("Brooklyn, NY");
let feelsLike = weather.current_condition[0].FeelsLikeF;
let description = weather.current_condition[0].weatherDesc[0].value;
await email({
subject: `Weather now is ${description} and ${feelsLike}°F`,
});
}
```
2. Click **Run**
3. Check your email inbox
You should have an email with a subject like "Weather now is Sunny and 67°F".
### Step 5: Schedule the val
We want this to run every morning. For example, in NYC, you could use `0 9 * * *` to run at 5am ET every morning.
We recommend [crongpt.com](https://crongpt.com) to help you write cron expressions. It also handles the timezone conversion from your timezone to Val Town's server timezone (UTC).
1. Go to [crongpt.com](https://crongpt.com) and generate your cron expression
2. Click the clock icon in the top right corner of the val editor
3. Click **Cron**
4. Paste in your cron expression
### Next steps
🥳 Congratulations! You've created a val that sends you a weather notification every morning. You can customize it to your needs. There are a LOT of weather vals you can use or gain inspiration from:
- [`Which_Jacket`](https://www.val.town/v/emilycoe/Which_Jacket) - email alerts about which jacket to wear
- [`umbrellaReminder`](https://www.val.town/v/stevekrouse/umbrellaReminder) - email alerts when it's going to rain
- [`weatherGPT`](https://www.val.town/v/stevekrouse/weatherGPT) - email alerts with a ChatGPT-written message
- [`powderNotify`](https://www.val.town/v/chet/powderNotifyCron) - email alerts when it snows
- [`sailingNotifyCron`](https://www.val.town/v/chet/sailingNotifyCron) - email alerts when it's a good day to sail
- [`discordWebhookWeatherHyd`](https://www.val.town/v/boson/discordWebhookWeatherHyd) - send weather updates to Discord
- [`weather_forecast_in_the_morning`](https://www.val.town/v/flafi87/weather_forecast_in_the_morning) - weather forecast on Telegram
- [`weatherBot`](https://www.val.town/v/jdan/weatherBot) - OpenAI Weather Bot via function calling
- [`aqi`](https://www.val.town/v/stevekrouse/aqi) - email alerts when AQI is unhealthy near you
- ...add yours here! (via the edit button below)
---
# Your first website val
URL: https://docs.val.town/quickstarts/first-website.md
import Val from "@components/Val.astro";
This is a step-by-step guide to making a personal website on Val Town. We'll use static server-rendered HTML for this example.
If you get stuck, please ask for help in the [Val Town Discord](https://discord.gg/dHv45uN5RY). We're friendly!
### Step 1: Sign up to Val Town
[Sign up to Val Town](https://www.val.town/auth/signup). It's free.
### Step 2: Fork this template
On the val below, click **⋮** > **Fork**.
You can leave the embedded window here any time by `cmd` / `ctrl` + `click`ing any link.
### Step 3: Customize your name, links, colors, etc
1. Edit your name in `` in `h1`.
2. Edit your self-description line.
3. Customize your links.
4. Customize the styles.
### Next steps
🥳 Congratulations! You now have a live website with a URL that you can share.
You can add a custom domain to your website in [Custom Domains](https://www.val.town/settings/domains). The following website were made with Val Town:
- [willkrouse.com](https://willkrouse.com)
- [stevekrouse.com](https://stevekrouse.com)
- [alex-we.in](https://alex-we.in/)
- [postpostscript-blog.web.val.run](https://postpostscript-blog.web.val.run/)
- ...add yours here! (via the edit button below)
---
# LLM prompting
URL: https://docs.val.town/quickstarts/prompting.md
import Val from "@components/Val.astro";
import { LinkButton, Tabs, TabItem} from "@astrojs/starlight/components";
Val Town provides multiple ways to leverage LLMs in your coding workflow:
- [**Townie**](#edit-single-vals-with-townie) for working with individual vals
- [**OpenTownie**](#edit-projects-with-opentownie) for working with multi-file projects
- [**Your favorite IDE**](#edit-projects-with-your-favorite-ide) (ie. Cursor, Windsurf, Zed, GitHub Copilot) for working with projects locally
- [**Your favorite LLM**](#general-llm-use) with `llms.txt` for any external AI assistant
### Edit Single Vals with Townie
**[Townie ↗](https://val.town/townie)** is a great AI assistant for creating and editing individual vals.
You can also access Townie by clicking `Edit in Townie` in any val.
- [Documentation](../../reference/townie)
- [View Townie's system prompt](https://gist.githubusercontent.com/stevekrouse/21cba8759c09f906be424dc1bb949869/raw/81caae98f1ce9b191374188d0f73efacbccba9a1/townie.md)
### Edit Projects with OpenTownie
**[OpenTownie (Beta) ↗](https://www.val.town/x/stevekrouse/OpenTownie)** is designed for editing multi-file [Val Town Projects](../../projects/).
- [Documentation](https://www.val.town/x/stevekrouse/OpenTownie/code/README.md)
- [View OpenTownie's system prompt](https://www.val.town/x/stevekrouse/OpenTownie/code/prompts/system_prompt.txt)
### Edit Projects with your Favorite IDE
#### Step 1: Setup
Use Val Town with your preferred IDE by first cloning your project locally with our [Val Town CLI](https://github.com/val-town/vt):
``` bash
# Install CLI
deno install -gAfr jsr:@valtown/vt
# Set API token
export VAL_TOWN_API_KEY=vtwn_replaceThis
# Clone project locally
vt clone my-project
```
#### Step 2: Add Val Town System Prompt
For each IDE, add our [system prompt](https://www.val.town/x/stevekrouse/OpenTownie/code/prompts/system_prompt.txt) as follows:
- **Cursor**: Add to [Project Rules](https://docs.cursor.com/context/rules-for-ai)
- **Windsurf**: Use [`@-mention` command](https://docs.windsurf.com/chat/overview#%40-mentions)
- **Zed**: Use [`/file` command](https://zed.dev/docs/assistant/commands)
- **GitHub Copilot**: Create [`.github/copilot-instructions.md`](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot) at the project root
### Your favorite LLM
Use our [llms.txt](../../llms.txt) and [llms-full.txt](../../llms-full.txt) with any LLM. The [llms.txt standard](https://llmstxt.org/) provide structured information to help LLMs understand Val Town.
You can also click `Copy page [in markdown]` or `Open in ChatGPT` on the top right of any docs page to chat about our docs with any LLM.
### Troubleshooting
If things go wrong:
- **Broken code?** Visit the "Versions" tab of your val or "History" of your project to revert
- **Complex changes?** Use [branches](../../projects/branches) and [merge](../../projects/pull-requests/) when stable
- **Code doesn't work?** Ask AI to debug the specific errors by copy and pasting, or screenshotting error messages
Not working? Get help on our [Discord](https://discord.val.town).
---
# Bot rate limiting
URL: https://docs.val.town/reference/bot-rate-limiting.md
We rate limit a small number of crawlers/bots that we identify by their
`User-Agent` Header. These bots generated an inordinate amount of traffic
(they are particularly fond of [playing chess](https://maxm-staticchess.web.val.run/)).
We've placed a global rate limit on their request volume. They are collectively
allowed 500 reqests every 5 minutes.
This list may change at any time, we'll do our best to keep it up to date.
| User-Agent |
|------------|
| `meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)` |
| `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)` |
| `Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)` |
| `Mozilla/5.0 (compatible; DataForSeoBot/1.0; +https://dataforseo.com/dataforseo-bot)` |
---
# Environment variables
URL: https://docs.val.town/reference/environment-variables.md
import Val from "@components/Val.astro";
You can store secrets, keys, and API token in two places:
- [Global Environment Variables](https://val.town/settings/environment-variables): For single Vals, but **not accessible in Projects**.
- Project Environment Variables: Added through the Project's left side bar.
Environment variables can be accessed via `Deno.env` or `process.env`.
- The "key" and "value" of each environment variable can be any string
- Vals can't set environment variables: Environment variables are set via the settings page. Trying to update an environment variable, for example by using `Deno.env.set`, is a no-op.
### Deno.env
This uses the Deno-default
[Deno.env](https://docs.deno.com/runtime/manual/basics/env_variables) variable,
which is available globally.
```ts
const secret = Deno.env.get("someSdkSecret");
export let sdk = new SomeSDK(secret);
```
### process.env
This is the conventional way to access environment variables when you're in a
Node.js environment.
```ts
const secret = process.env.someSdkSecret;
export let sdk = new SomeSDK(secret);
```
## Environment variables are private in public vals
It is safe for a public val to reference your environment variables.
Others can see that they're being used, but not their values.
For example, in this public script val, you can see that I'm using a discord bot's environment variable, but you cannot run this code or get the value of the environment variable.
If you expose an HTTP or email val, those vals can be _triggered_ by anyone who knows their URL or email address, but they still can't access your environment variables. This is how server-side code normally works. If someone wants to run your code with _their_ environment variables, they need to _import_ your code and run it in their own vals.
---
# esm.town
URL: https://docs.val.town/reference/esm-town.md
esm.town is the home of source code hosted on Val Town. Every val has a corresponding esm.town URL – simply replace `val.town` with `esm.town` in the URL to get the source code for that val. Vals import code from other vals using their esm.town URL.
For example, https://val.town/v/stevekrouse/demoSDK has its source code available at https://esm.town/v/stevekrouse/demoSDK.
There are a few things to note about esm.town:
- This endpoint will always redirect to a specific version (`?v=15`)
if a version isn't provided initially.
- We detect your client - whether it's a browser, Deno, or something
else, and try to serve raw TypeScript source code when the client
is able to parse it, and transpiled JavaScript when it can't.
This includes [JSX syntax](/types/http/jsx/).
See [our guide on interoperability](/guides/interop/) for more
information on using vals in other systems.
- Private vals are accessible on esm.town only by providing a
[Bearer token](/api/authentication).
---
# Importing
URL: https://docs.val.town/reference/import.md
One of the best features of Val Town is the ability to import modules. There are thousands
of modules written in JavaScript and TypeScript that you can instantly
import to extend and supercharge your vals.
Val Town supports importing code from a variety of sources, including other vals on Val Town,
[NPM](https://www.npmjs.com/), [JSR](https://jsr.io), [esm.sh](https://esm.sh/), [deno.land](https://deno.land/x).
You can also import code from arbitrary URLs if they provide the correct `Content-Type` header
and contain JavaScript or TypeScript.
Imports in Val Town work the same as in Deno, so you can [learn more from their docs](https://docs.deno.com/runtime/manual/basics/modules/).
## Importing vals
A val can reference other vals. You will get helpful autocomplete for importing other vals if
you type the `@` symbol.
```ts
import { example1 } from "https://esm.town/v/stevekrouse/example1";
```
For projects, you can import files and folders relative to the project root (ie. `./home`), which means you don't have to update your imports when you branch, fork, or rename a project.
All vals are hosted on [esm.town](/api/esm-town),
and are available for import on Val Town, Deno, Node, the browser, and anywhere else https-imports are supported.
In this way, Val Town builds upon the new http-style imports, to make a lightweight package registry,
but where each snippet of code is a package.
Private vals are only importable with the `DENO_AUTH_TOKEN` environment variable set, which we
automatically set for you, allowing only you to import your private vals.
## NPM
To import an npm module, like [zod](https://www.npmjs.com/package/zod), add
`npm:` in front of the name. So,
```ts
import { z } from "npm:zod";
```
:::note[NPM compatibility and the sandbox]
Most NPM modules are supported and will work great in Val Town. However,
we do run in a security sandbox: a val can't access the filesystem. Some
NPM modules try to do that, and won't be able to. Using esm.sh
is often an effective workaround for these modules. See
[permissions errors](/troubleshooting/permission-errors/) for more
information.
:::
## JSR
Val Town supports importing [JSR](https://jsr.io) modules. Simply add `jsr:` and `@` in front of the module name:
```ts
import { concat } from "jsr:@std/bytes";
```
## HTTP Imports
To import from a URL, like from `esm.sh`, you just specify the full url. For
example, to import `zod` from `esm.sh`:
```ts
import { z } from "https://esm.sh/zod";
```
## Node builtins
If you want to import a Node.js built-in module, add `node:` in front of its
name. For example, to import the
[crypto](https://nodejs.org/dist/latest-v20.x/docs/api/crypto.html) module, use
this, which is the same as what Node.js themselves officially recommend:
```ts
import { createHmac } from "node:crypto";
```
## Debugging imports
### Look for examples
Whenever you go to use a new npm library, we recommend first searching to see if
anyone has used that library before.
[You can find the most common packages here](https://www.val.town/examples/packages).
Or you could look for examples related to `cheerio` by searching for it or even
more pointedly by searching for [`"npm:cheerio"`](https://www.val.town/search?q=%22npm:cheerio%22).
### Log keys
It's often not clear how a library will expose its methods. It can be helpful to
log the keys of whatever object they return to you. That will tell you if you
need to import it off of default or not, and what is available to you.
```ts title="Example" val
import * as cheerio from "npm:cheerio";
console.log(Object.keys(cheerio));
```
```txt title="Logs"
[
'contains', 'default',
'html', 'load',
'merge', 'parseHTML',
'root', 'text',
'xml'
]
```
### Look for Deno compatibility
Not all modules are compatible with Deno.
Some modules are written with only Node.js or a browser environment in mind, and
won't work with Deno. While Deno implements most of the functionality of Node.js
and some of the functionality of browsers - so many modules will "just work" in
it, some won't.
Also, some modules are written with the assumption that they can access system
resources, compile C code, and more, so they won't work in a secure environment
like Val Town.
:::tip[We're here to help]
As always, [ask in our Discord if you want help](https://discord.val.town/).
:::
---
# Permissions
URL: https://docs.val.town/reference/permissions.md
There are three privacy settings for a Val:
### Public
- Discoverable and visible to everyone on the Val Town website
- Anyone can view the code and import it into their own vals
- HTTP endpoints are accessible to anyone
### Unlisted
- Only visible to people who have the direct link
- Won't appear in search results or public listings
- Anyone with the link can view the code and import it
- HTTP endpoints are accessible to anyone who knows the URL
### Private
- Only visible to you
- Only your vals can import the code
- HTTP endpoints are accessible to anyone who knows the URL
## Exposing your vals to the internet
HTTP endpoints are accessible to anyone who knows the URL, regardless of the val's privacy setting. If your endpoint handles sensitive data, you should add authentication.
Since anyone can call your endpoints, if they interact with some data
that should only be changed by yourself, you will need to make sure that those
endpoints check for some kind of secret that only you know.
Here's an example of a val exposed using the [HTTP Val](/types/http), secured
with an environment variable that only I know:
```ts title="user/secretEndpoint" val
export const secretEndpoint = (req: Request) => {
const secretHeader = req.headers.get("Authorization");
if (secretHeader !== Deno.env.get("supersecrettoken")) {
return new Response("Unauthorized", { status: 401 });
}
return new Response("My deepest darkest secret");
};
```
If I called it without supplying the environment variable, I'd be denied access:
```ts title="Without authentication" val
import { fetch } from "https://esm.town/v/std/fetch";
const response = await fetch("https://user-secretEndpoint.web.val.run");
console.log(response);
```
```js title="Logs"
Response {
body: ReadableStream { locked: false },
bodyUsed: false,
headers: Headers { /* omitted */ },
ok: false,
redirected: false,
status: 401,
statusText: "Unauthorized",
url: "http://localhost:3001/v1/fetch?url=https%3A%2F%2Fuser-secretEndpoint.web.val.run"
}
```
By supplying the environment variable in a header, I'm allowed access:
```ts title="With authentication" val
import { fetch } from "https://esm.town/v/std/fetch";
const response = await fetch("https://user-secretEndpoint.web.val.run", {
headers: { Authorization: "birdsarentreal" },
});
console.log(response);
```
```js title="Logs"
Response {
body: ReadableStream { locked: false },
bodyUsed: false,
headers: Headers { /* omitted */ },
ok: true,
redirected: false,
status: 200,
statusText: "OK",
url: "http://localhost:3001/v1/fetch?url=https%3A%2F%2Fuser-secretEndpoint.web.val.run"
}
```
## Public code referencing private data
It is safe for a a public val to reference one of your private vals or one of
your environment variables. Private vals are like environment variables in this
way — others can see that they're being used, but not their values.
For example, I created a private val, `example3`. You won't be able to see or
reference `example3` but I can use it in `example4` which is public.
```ts title="@user/example4"
import { example3 } from "https://esm.town/v/user/example3";
console.log("Hi,", example3);
```
```txt title="Logs"
Hi, User
```
You can _infer_ that the value of `example3` is `"User"` because of
how it's used here. This is why you have to be careful about publishing vals
that reference private data. Typically you will reference private data in a way
that makes it impossible for others to infer what it is, like you would with an
environment variable credentials. Below I am passing my environment variables to an Upstash
Redis store. You can see that I'm using these environment variables and the output of this
computation, but you can't get those values, nor can you rerun this script with
my environment variables.
```ts title="Example" val
import { Redis } from "npm:@upstash/redis";
const redis = new Redis({
url: Deno.env.get("upstashURL"),
token: Deno.env.get("upstashToken"),
});
await redis.set("json-ex-1", { a: { b: "nested json" } });
const get = await redis.get("json-ex-1");
console.log(get);
```
```ts title="Output"
nested json
```
## Using another's vals as a library
You can [import](/reference/import) any Public or Unlisted vals and use
them in your own code. In this way
it is safe to pass other's code your private data and environment variables.
```ts title="Example" val
import { gpt3 } from "https://esm.town/v/patrickjm/gpt3?v=4";
export let librarySecret = gpt3({
prompt: "what is the meaning of life?",
openAiKey: Deno.env.get("openai"),
});
```
:::danger[Import security]
If you're importing someone else's code, read the code first to make
sure it's trustworthy, and we recommend version-pinning any dependencies
against other people's code, like `?v=4` in the example above.
:::
## Custom Authentication
You can roll arbitrary authentication schemes in user space. For example, I find
it useful to simply supply a custom `auth` header from my Clerk webhooks that I
check like a password against a value I store in my environment variables:
```ts title="Clerk example" val
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
// # New Val Town User (on Clerk) -> Val Town Discord notification
// Translates one kind of webhook (Clerk) into another (Discord)
export async function handleDiscordNewUser(req: express.Request, res) {
// check custom auth secret sent from clerk
if (req.get("auth") !== process.env.clerkNonSensitive)
return res.end("Unauthorized");
await discordWebhook({
url: Deno.env.get("discordUserEvents"),
content:
req.body.data.email_addresses[0].email_address +
" " +
req.body.data.profile_image_url,
});
res.end("Success");
}
```
I call this value `clerkNonSensitive` because this value doesn't protect any
data. It merely makes it impossible for anyone to trigger this public API
endpoint without the password. The worst that could happen if this password
leaks is that our team temporarily gets spam discord messages. Then I could just
change the password to a new value. For more sensitive use-cases, you'll want to
sign & possibly encrypt the conveyed data using standard authentication methods.
## Legacy Private Vals
Prior to February 12th, 2024, private HTTP vals required an API token to access their endpoints. The endpoint was only accessible to the val owner and wasn't visible on the Val Town website.
This behavior was changed - HTTP endpoints are now accessible to anyone who knows the URL, regardless of privacy setting. Private vals created before this date have been given a `Legacy private` permission setting to preserve their original behavior.
---
# Runtime
URL: https://docs.val.town/reference/runtime.md
import { LinkCard } from "@astrojs/starlight/components";
Val Town code runs on the [Deno](https://deno.com/) runtime.
- TypeScript and TSX are available for all vals, and at the time of this
writing we support TypeScript 5.6.2.
- There is no filesystem access permitted.
- `--allow-net` is enabled, so they can make HTTP requests.
- Vals have access to [environment variables](/reference/environment-variables)
- The experimental [Temporal API](https://tc39.es/proposal-temporal/docs/) is
enabled.
## Included environment
- `fetch` to make HTTP requests
- `import` from npm and https
### Versions
We run the same version of Deno, V8, and TypeScript for all vals.
At the time of this writing (likely out of date), we run:
- Deno 2.1.2
- V8 13.0.245.12-rusty
- TypeScript 5.6.2
---
# Version Control
URL: https://docs.val.town/reference/version-control.md
Vals are immutable, but you can publish new versions of vals. Versions start at
`0` and go up from there. When you reference a val, you always get the most
recent version. You can toggle which version you're looking at via the dropdown
menu next to the val's name.
## Imports & pinning
Vals are imported via esm.town URLs. If you leave off the version number, it's an unpinned import, and you'll always get the latest version. If you include a version number, it's a pinned import, and you'll always get that version.
```ts title="Unpinned val import"
import { myVal } from "https://esm.town/v/username/valname";
```
```ts title="Pinned val import"
import { myVal } from "https://esm.town/v/username/valname?v=7";
```
When you import one of your own vals, via the `@` helper, it will be imported unpinned, so you will
automatically get all new changes. When you import another user's val via `@`,
it will pin your import to the most recent version of that val
(ie it will end it `?v=7` or whatever the current version number is),
so if they change it, you won't get automatic updates.
This makes your code more stable and protects you against
code injection attacks. You can override this default behavior if you trust the
user not to break your code: remove the `v` query param in the import statement,
and your code will always use the latest version of that val.
### Versions of external imports
You can also specify versions of NPM modules by using an `@` symbol:
```ts
import { min } from "npm:lodash-es@4.17.21";
// Or you can specify a version range, like
// you would in a package.json file:
import { min } from "npm:lodash-es@4";
```
We highly recommend pinning imported modules because their APIs can
change between versions and unexpectedly break your code.
### Lockfiles
When you save a Val we generate a
[deno.lock](https://docs.deno.com/runtime/manual/basics/modules/integrity_checking/)
lockfile. This pins all versions of your dependencies and also calculates hashes
to verify that the content of a dependency does not change.
When you save a Val, we regenerate the lockfile from scratch, re-pinning your
unpinned dependencies to whatever their current versions are at that new time.
Additionally, when you save a val, we find all the vals in your account that
depend on that val and mark their lockfiles for regeneration. That way you can
keep rapidly iterating and we'll make sure to update dependency relationships in
your vals.
Read more about how and why we use lockfiles here: https://blog.val.town/blog/lockfiles/
You can also use a [lockfile import map](https://www.val.town/v/maxm/lockfileImportMap) if you need to make sure your val frontend and backend are always in sync.
---
# Keyboard shortcuts
URL: https://docs.val.town/reference/shortcuts.md
- `ctrl/cmd-enter` or `shift-enter` to run a val
- `ctrl/cmd-s` to save a val without running it
- `ctrl/cmd-k` to open the command menu, which lets you create vals,
navigate, and more.
- `/` to trigger a search within the command menu.
- `ctrl/cmd-click` to go to a val reference or URL
- `ctrl+space` to trigger autocomplete in the code editor
---
# Townie
URL: https://docs.val.town/reference/townie.md
[Townie][] is an AI assistant that can help you create and edit vals in Val Town.
You can use Townie to create backend services, websites, and other vals
that are deployed instantly to quickly iterate on an idea.
## Get started
To create a new val based on your own prompts,
navigate to [Townie][], type in a prompt, and hit submit.
Once Townie has created a val, you'll see a preview of the live code.
You can continue the conversation to iterate on your val further or make manual edits to the code if needed.
## HTTP val Template
Townie's default system prompt includes a template that is used as a basis for vals created in Townie. The template is intended to work well for code that can run on the server or in a client-side context. By default, it'll tend to use React to build front-end interfaces
and the built-in [SQLite][] storage for persisting data.
To change what libraries or approaches Townie uses, you can include instructions in the prompt. For example, you could say _"Use Hono instead of React."_
## Basic prompt-writing tips
- Saying something like _"Make it better"_ can sometimes yield iterative improvements.
- Try asking Townie to brainstorm multiple approaches for comparison before writing code.
- Provide instructions as a numbered list to have Townie make multiple changes in a single go.
## Using a custom system prompt
Townie uses a system prompt to generate code that is appropriate for Val Town.
If you'd like to adjust the prompt for a specific use-case, you can edit the default
prompt used in Townie's settings.
## Share a pre-filled prompt
`townie.new?prompt=` and `val.town/townie?prompt=` let you link directly to a new Townie chat with the input box pre-filled, like [this](https://www.val.town/townie?prompt=Build%20my%20own%20%E2%80%9Clink%20in%20bio%E2%80%9D%20page).
:::note
Third-party apps can integrate Townie into their product flow, for example.
If you end up doing this, we'd love to [hear about it](mailto:hi@val.town)!
:::
To prevent malicious inputs, the recipient must hit enter manually to submit the prompt.
## Troubleshooting
### Diffs
You can ask Townie to "respond with a diff." For small changes, this works well, but can be error prone on larger diffs or when Townie hallucinates.
### Checking for errors
If Townie produces code with errors, it attempts to catch these errors and recover from them.
There are two places to check for errors in HTTP vals: in the val's _Requests_ log or in your browser's console, depending on where the error occurs.
### Recovering from errors
Sometimes Townie can make mistakes and cause errors.
These can usually be recovered from by using specific prompts.
If Townie omits parts of the code or a diff patch fails, try replying with a phrase like _"Respond with the full code"_.
### Starting from scratch
When conversations grow in length, Townie may have a tendency to hallucinate or create errors. Starting a new chat thread on the same val can sometimes help.
[townie]: https://val.town/townie
[http vals]: /types/http
[script vals]: /types/script
[cron vals]: /types/cron
[SQLite]: /std/sqlite
---
# Blob Storage
URL: https://docs.val.town/std/blob.md
Val Town comes with blob storage built-in. It allows for storing any data, like text, JSON, or images. You can access it via [`std/blob`](https://www.val.town/v/std/blob).
Blob storage is scoped globally to your account. If you set a blob in one val, you can retrieve it by the same key in another val. It's backed by Cloudflare R2.
### Blob Admin Panels
- [Blob Storage in Settings](https://www.val.town/settings/blob) – built-into Val Town - list, download, delete blobs
- [Blob Admin](https://www.val.town/v/stevekrouse/blob_admin) – search, view, edit, upload blobs – built in a val – easy to customize in Val Town!
### Usage
##### Get JSON
```ts title="Get JSON"
import { blob } from "https://esm.town/v/std/blob";
let blobDemo = await blob.getJSON("myKey");
console.log(blobDemo); // returns `undefined` if not found
```
##### Set JSON
```ts title="Set JSON"
import { blob } from "https://esm.town/v/std/blob";
await blob.setJSON("myKey", { hello: "world" });
```
##### List keys
```ts title="List Keys"
import { blob } from "https://esm.town/v/std/blob";
let allKeys = await blob.list();
console.log(allKeys);
const appKeys = await blob.list("app_");
console.log(appKeys); // all keys that begin with `app_`
```
##### Delete by key
```ts title="Delete"
import { blob } from "https://esm.town/v/std/blob";
await blob.delete("myKey");
```
### Examples
- [Counter](https://www.val.town/v/std/docsBlobCounterDemo)
- [RSS Notifications](https://www.val.town/v/stevekrouse/rssNotifyExample) (saving the last run time)
- Picture: [Save](https://www.val.town/v/andreterron/blobSavePictureExample) & [Read](https://www.val.town/v/andreterron/blobReadPictureExample)
### Error Handling
- `blob.get` can throw [`ValTownBlobNotFoundError`](https://val.town/v/std/ValTownBlobNotFoundError)
- Any method can throw [`ValTownBlobError`](https://val.town/v/andreterron/ValTownBlobError) for unexpected errors.
### Utilities
Our Blob SDK also includes some utility functions to make working with blobs easier.
##### Copy
```ts title="Copy"
import { blob } from "https://esm.town/v/std/blob";
await blob.copy("myKey", "myKeyCopy");
```
##### Move
```ts title="Move"
import { blob } from "https://esm.town/v/std/blob";
await blob.move("myKey", "myKeyNew");
```
### Lower-level API
We provide access to the lower-level getter and setters,
which are useful if you are storing non-JSON or binary data,
need to stream in your response or request data, or do anything else lower-level.
- `async get(key: string)`: Retrieves a blob for a given key.
- `async set(key: string, value: string | BodyInit)`: Sets the blob value for a given key. See [BodyInit](https://deno.land/api@v1.38.1?s=BodyInit).
### Limitations
- Blob-stored data counts towards your total Val Town storage – 10mb on the free plan and 1gb on pro. Check our [pricing page](https://val.town/pricing) to learn more.
- Keys for blobs can be up to 512 characters long.
---
# OpenAI
URL: https://docs.val.town/std/openai.md
Use OpenAI's chat completion API with [`std/openai`](https://www.val.town/v/std/openai). This integration enables access to OpenAI's language models without needing to acquire API keys.
For free Val Town users, [all calls are sent to `gpt-4o-mini`](https://www.val.town/v/std/openaiproxy?v=12#L85).
## Basic Usage
```ts title="Example" val
import { OpenAI } from "https://esm.town/v/std/openai";
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
messages: [
{ role: "user", content: "Say hello in a creative way" },
],
model: "gpt-4",
max_tokens: 30,
});
console.log(completion.choices[0].message.content);
```
## Images
To send an image to ChatGPT, the easiest way is by converting it to a
data URL, which is easiest to do with [@stevekrouse/fileToDataURL](https://www.val.town/v/stevekrouse/fileToDataURL).
```ts title="Image Example" val
import { fileToDataURL } from "https://esm.town/v/stevekrouse/fileToDataURL";
const dataURL = await fileToDataURL(file);
const response = await chat([
{
role: "system",
content: `You are an nutritionist.
Estimate the calories.
We only need a VERY ROUGH estimate.
Respond ONLY in a JSON array with values conforming to: {ingredient: string, calories: number}
`,
},
{
role: "user",
content: [{
type: "image_url",
image_url: {
url: dataURL,
},
}],
},
], {
model: "gpt-4o",
max_tokens: 200,
});
```
## Limits
While our wrapper simplifies the integration of OpenAI, there are a few limitations to keep in mind:
- **Usage Quota**: We limit each user to 10 requests per minute.
- **Features**: Chat completions is the only endpoint available.
If these limits are too low, let us know! You can also get around the limitation by using your own keys:
1. Create your own API key on [OpenAI's website](https://platform.openai.com/api-keys)
2. Create an [environment variable](https://www.val.town/settings/environment-variables?adding=true) named `OPENAI_API_KEY`
3. Use the `OpenAI` client from `npm:openai`:
```ts title="Example" val
import { OpenAI } from "npm:openai";
const openai = new OpenAI();
```
---
# CORS
URL: https://docs.val.town/troubleshooting/cors.md
import { LinkButton } from "@astrojs/starlight/components";
This document explains how Val Town handles Cross-Origin Resource Sharing (CORS)
headers, why they're important, and how to customize them for your vals.
Live demo
## Default CORS Configuration
By default, Val Town adds the following CORS headers to responses:
```
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
```
When responding to preflight requests, we also mirror any requested headers in
the `Access-Control-Allow-Headers` response header.
These default headers enable your vals to:
- Be called from any domain (`*`)
- Support common HTTP methods without additional configuration
- Work with standard web APIs and frameworks
- Handle preflight requests automatically
This default configuration is beneficial for development, allowing easy
interaction with your Val endpoints. However, as you move towards production,
you may want to implement more restrictive CORS policies for enhanced security.
## Customizing CORS Headers
You can override the default CORS configuration by setting your own CORS headers
in your val. Once you set any CORS-related header, Val Town will not add any
default headers.
### Example: Custom CORS Configuration
```ts title="Custom Cors" val
export async function myEndpoint() {
return new Response("Hello", {
headers: {
"Access-Control-Allow-Origin": "https://specific-domain.com",
"Access-Control-Allow-Methods": "GET,POST",
},
});
}
```
### Example: Handling Preflight Requests
For complete control over CORS behavior, you can handle OPTIONS requests
explicitly:
```javascript title="Handle Preflight Request" val
export async function myEndpoint(req) {
if (req.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "https://specific-domain.com",
"Access-Control-Allow-Methods": "GET,POST",
"Access-Control-Allow-Headers": "*",
"Access-Control-Max-Age": "86400",
},
});
}
// Handle the actual request
return Response.json({ message: "Hello" });
}
```
## Removing CORS Headers
If we detect that you've set your own `"Access-Control-Allow-Origin"` header we
won't add any custom CORS headers to your request.
```javascript title="Remove CORS Headers" val
export async function myEndpoint() {
return new Response("Hello", {
headers: {
"Access-Control-Allow-Origin": "", // The minimum possible configuration to disable our headers.
},
});
}
```
## Security Considerations
While the default CORS configuration enables broad access (`*`), consider
restricting the `Access-Control-Allow-Origin` header to specific domains in
production environments where appropriate. This can help prevent unauthorized
cross-origin requests to your vals.
---
# Migrating Deprecated HTTP Vals
URL: https://docs.val.town/troubleshooting/migrating-deprecated-http-vals.md
We're migrating all HTTP vals to a new runtime. Vals on the old runtime are
labeled "HTTP (deprecated)" in the UI.
For a comprehensive guide on migrating your HTTP vals, please refer to our
[Migration Checklist](#migration-checklist).
The new runtime executes code slightly differently, which will impact a very small number of vals.
Here's what you need to know:
### Accidentally re-using values.
Previously, HTTP vals executed all code for each request, generating new values
every time. For example, this val would return a different random number on each
request:
```ts val
const randomValue = Math.random();
export default async function (req: Request): Promise {
return Response.json({ randomValue });
}
```
```bash
curl https://std-rand.web.val.run # => {"randomValue":0.35380812508500936}
# => {"randomValue":0.48797154766298423}
# => {"randomValue":0.31486102144764216}
```
Our new runtime reuses Deno processes between requests when possible. This means
values defined outside the request handler will be shared across requests:
```bash
curl https://std-rand.web.val.run # => {"randomValue":0.22669880732624748}
# => {"randomValue":0.22669880732624748}
# => {"randomValue":0.22669880732624748}
```
To fix this, you can move the call to `Math.random()` inside the request
handler.
```ts val
export default async function (req: Request): Promise {
const randomValue = Math.random();
return Response.json({ randomValue });
}
```
This will generate a new random value on every request, and return a new random
value, just like before.
```bash
curl https://std-rand.web.val.run # => {"randomValue":0.7862069462810053}
# => {"randomValue":0.33484196890793516}
# => {"randomValue":0.7272455873033288}
```
### Intentionally caching values for performance!
Sometimes, you may want to reuse values across requests for performance reasons.
The new runtime's behavior can be advantageous in these scenarios. You can
initialize values or perform expensive operations outside the request handler,
and they'll be cached between requests:
```ts val
// Expensive initialization or data fetching
const expensiveData = await fetchLargeDataset();
const cache = new Map();
export default async function (req: Request): Promise {
const url = new URL(req.url);
const key = url.searchParams.get("key");
if (cache.has(key)) {
return Response.json({ data: cache.get(key) });
}
const result = expensiveComputation(key, expensiveData);
cache.set(key, result);
return Response.json({ data: result });
}
```
In this example:
1. `fetchLargeDataset()` is called only once when the val is initialized, not on
every request.
2. We create a `cache` Map that persists between requests.
3. The request handler checks the cache first, potentially saving computation
time.
4. If the result isn't cached, it performs the computation and stores the result
for future requests.
This approach can significantly improve performance for repeated requests or
when dealing with expensive initializations. However, be mindful of memory
usage, as cached data will persist in memory until the val is restarted.
### Migration Checklist
When migrating your HTTP vals to the new runtime:
1. Review your code for any variables declared outside the request handler.
2. Determine if these variables should be regenerated for each request or if
they can be safely cached.
3. Move variables that need to be unique per request inside the handler
function.
4. Consider opportunities to improve performance by intentionally caching
expensive computations or initializations.
5. Test your val thoroughly to ensure it behaves as expected under the new
runtime.
If you run into any issues during your migration please reach out to us at
[hi@val.town](mailto:hi@val.town).
---
# Serialization
URL: https://docs.val.town/troubleshooting/serialization.md
Val Town is a server-side platform, primarily: when you run code, it runs
on a server. This has many benefits, like the ability to import many different
modules, make requests to other servers, run code on a schedule, and much
more.
It also means that when you run code and see the results of that code, the
results have to be stored, encoded, sent to your browser, and then decoded
into something readable. We try to cover as many bases as possible with this
conversion: if your code returns a JavaScript string, number, array, Map,
Set, and many other types - we'll show you a very helpful representation of it.
However, there are some objects that aren't really translatable from one
environment to another, like `Promise` and `Symbol` objects. In some cases as well, code that returns data with recursive structures - self-references -
will fail to be serialized, and produce an error.
---
# Upgrade to Deno
URL: https://docs.val.town/upgrading/upgrade-to-deno.md
Val Town was originally built on a [NodeJS](https://nodejs.org/en) runtime. On
March 14th, 2023 we cut over to a [Deno](https://deno.com/)-based runtime. There
are a few breaking changes with the new runtime, enumerated below.
If you find that the new runtime has broken some of your code, we are happy to
help you upgrade - just contact us via Discord or email.
## Breaking Changes
### User-defined functions can be synchronous 🥳
In the old runtime, you would need to `await` the call to any `@user.function`.
That is no longer the case! Now only async functions are async and synchronous
functions don't need to be awaited!
Happily this works without many changes because if you `await` something that's
not a `Promise` in JavaScript, it simply returns that value. However if you
called `.then` on the value returned by another user's synchronous function,
that will no longer work in the new runtime.
### `setInterval` has been removed
Use Cron Vals instead.
### `setTimeout` has been removed
Contact us if you need this functionality.
### Promises are not recursively resolved between functions
In the prior runtime, all values were recursively awaited. This means that if
you returned an array or object with a Promise nested somewhere inside it, we
would've resolve that Promise to the underlying value in between all function
calls. This is too much magic, and we no longer do it. The main place that this
breaking change is felt is when you return an array of promises. Now you need to
wrap it with `Promise.all`, like you would in normal JavaScript. We manually
went through all instances of this we could find in scheduled vals and upgraded
it by hand.
We still unwrap (auto await) promises recursively for you at the very top level
of val evaluation.
### Val reference error returns `undefined` instead of throwing
If you referenced `@stevekrouse.valThatDoesntExist` it used to throw a runtime
error. Now it returns `undefined`.
### You can't use `export` without proper variable assignment
You used to be able to write `export a = 1`, but now that will be automatically
converted to `export let a = 1`, in order to make it syntactically valid
JavaScript.
### ExpressJS types moved to `express` namespace
If you wanted Typescript support for making ExpressJS handler vals, you used to
add `Request` and `Response` types to your val's arguments. However we had to
wrap those types in an `express` namespace, so they are now `express.Request`
and `express.Response`.
The reason is to make room for the browser-standard `Response` type that is the
result of a `fetch` request. The following image shows all three types used in
parallel:

### `req.body` is no longer overloaded by `req.query`
In our old runtime, we shoved any `req.query` params into `req.body` in order to
try to improve the DX of ExpressJS. We no longer do this because it was too much
magic. Now the Express fields behave the way the Express docs say they do.
## Non-breaking changes
### Cron evaluations has `0` arguments where it used to have `1`
On your [intervals page](https://www.val.town/settings/intervals), you may
notice that where it used to say that crons have `1` argument (circled below
in red), it now says they take `0` arguments (circled below in purple). This
does not affect that fact that crons pass the `Interval` object to the
function that has been scheduled, which allows you to get the `lastRunAt` value
of the interval in your cron vals.

### Untitled val names are generated differently
If you created a val without naming it, we will generate a random untitled name
for it slightly differently than before:
`@stevekrouse.untitled5675310` → `@stevekrouse.untitled_uYhSGXfe`
---
Thanks [Tim Reichen](https://github.com/timreichen) for the MIT-licensed Deno
artwork at the top!
---
# Simplified val privacy
URL: https://docs.val.town/upgrading/simplified-val-privacy.md
Publishing a Val has been one of the biggest sources of confusion in Val Town.
We rolled out a simpler model: Val privacy status is now entirely controlled in
the UI. We sent personalized emails to any users affected by this change.
### Val Privacy Was Confusing
1. Vals were made public by adding `export` and private by leaving out `export`
at the beginning of the code
2. Privacy was tied to specific versions of a Val. So v0 could be private, and
v1 could be public.
### The Fix
1. Val privacy status is now controlled entirely in the UI (click the 🔒 or
🌐 icon)
2. A Val is public or private: all versions of the val have the same privacy
setting.
### Upgrading
This had no effect for most users. However some of you did have Vals with mixed
privacy. That is, you had a single val where some versions were public and some
versions were private. We automatically made all of those Vals private and
emailed you to let you know what we did, and to which Vals, so you could
re-publish any that were actually meant to be public. If you did not get an
email then you need not take any action.
Going forward our runtime will ignore the `export` keyword. You can only affect
the privacy of a Val via the UI. There is currently no way to change the privacy
status of Val via the API, but we plan to add that shortly.
---
# Cron (Scheduled)
URL: https://docs.val.town/vals/cron.md
import Val from "@components/Val.astro";
Cron jobs let you schedule code to run repeatedly on a schedule.
Some common examples include:
- Checking an RSS feed every hour
- Getting notified when a website updates
- Sending a reminder email every morning
Cron functions can be configured with [cron syntax](https://en.wikipedia.org/wiki/Cron), or simple intervals like "once an hour"
Functions can run up to once every 15 minutes, or [once a minute, with the Pro plan](https://www.val.town/pricing).
## Type Signature
Cron functions should take an `Interval` object as a parameter, and can return anything as a result. The return value is ignored.
```tsx title="Example" val
export function cronValHandler(interval: Interval) {
console.log("Cron val ran!");
}
```
The Interval type has the following shape:
```ts
interface Interval {
lastRunAt: Date | undefined;
}
```
## Schedule types
There are two kinds of schedules: either simple intervals, like "run every two hours",
or you can use cron syntax to define more complex schedules.
You can consult [crongpt.com](https://crongpt.com) for help on writing cron expressions.
For example the following cron runs every two hours, on the first day of the month:
```
0 */2 1 * *
```
:::note[Crons run in UTC]
Cron expressions are always evaluated in the [UTC timezone](https://en.wikipedia.org/wiki/Coordinated_Universal_Time),
so if you want to specify a certain hour in your local timezone, you may need to adjust
the cron expression.
:::
## Example
This example polls an RSS feed for new items since the last run. This val runs every 1 hour.
---
# Email
URL: https://docs.val.town/vals/email.md
import Val from "@components/Val.astro";
Email vals get their own email address that you can send email to. When Val Town
receives that email, it triggers the val with the email as its first argument.
Some common examples include:
- Automatically forwarding emails
- Posting support team emails to Discord / Slack
:::tip
Vals can send email, too! Using the [email function in the standard library](/std/email)
:::
## Type Signature
Email vals receive an argument called `Email` that represents the email that was sent to the val. Here's an example of an email val:
```ts title="Example"
export async function emailValHandler(email: Email) {
console.log("Email received!", email.from, email.subject, email.text);
for (const file of email.attachments) {
console.log(`Filename: ${file.name}`)
console.log(`Content Type: ${file.type}`)
console.log(`Content: ${await file.text()}`)
};
}
```
The `Email` type has this shape:
```ts
interface Email {
from: string;
to: string[];
cc: string | string[] | undefined;
bcc: string | string[] | undefined;
subject: string | undefined;
text: string | undefined;
html: string | undefined;
attachments: File[];
}
```
## Example
This val forwards any email it receives to me. Try it out by sending an email to `stevekrouse.forwarder@valtown.email`.
## Limitations
:::note[Email size limit]
The total size of inbound emails, including attachments, must be less than 30MB.
:::
---
# Express
URL: https://docs.val.town/vals/express.md
:::caution[Deprecated]
New vals [should use the Web API instead](/types/http), which supports a wider
range of inputs & outputs.
:::
The Express API allows to run a Val as an [Express](https://expressjs.com/)
route handler. The Express system will be familiar to folks who have written web
servers with Node.js, and gives you control over details like response headers
and redirection.
You can return HTML
([example val](https://www.val.town/v/stevekrouse.expressHTMLExample),
[output](https://api.val.town/v1/express/stevekrouse.expressHTMLExample?name=Townie))
or define a webhook handler to adhere to another service's specifications
([example val](https://www.val.town/v/stevekrouse.handleOnboardingReferral)).
The Val must be a function. It is passed two arguments, the
Express **[req](https://expressjs.com/en/4x/api.html#req)** and **[res](https://expressjs.com/en/4x/api.html#res)** objects.
You can use **`req`** to pull out request data, and **`res`** to respond with
any valid Express response. Learn more at
the [Express docs](https://expressjs.com/en/4x/api.html).
Unauthenticated use will only be able to call public vals as Express handlers.
The Val will be executed with Val author's permissions, so it will be able to
read and write to author's public and private vals, read their environment variables, and send
them emails via **`console.email`**.
:::note[Not all express methods are included]
For security reasons, the versions of the express Request and
Response objects exposed to vals don't expose all methods. Requests have the
methods: `method` `query` `body` `baseUrl` `params` `secure` `subdomains`
`fresh` `protocol` `path` `originalUrl` `acceptsCharsets` `acceptsEncodings`
`acceptsLanguages` `accepts` and `get`. Responses have the methods `json`
`jsonp` `status` `send` `type` `get` `redirect` `end` `set` .
:::
## `handle-val.express.val.run`
The Express API can be called via GET or POST. You can access the JSON-parsed
POST request body via `req.body`:
```ts title="@user/postWebhook" val
export let postWebhook = (req: express.Request, res: express.Response) => {
res.json({ data: `Hello + ${req.body.name}!` });
};
```
```ts title="Fetch @user/postWebhook" val
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON";
export let postWebhookTest1 = fetchJSON(
"https://user-postWebhook.express.val.run",
{
method: "POST",
body: JSON.stringify({ name: "Steve" }),
}
);
```
Check out the [ExpressJS docs](http://expressjs.com/) to figure out how to use
the `req` and `res` objects or add `express.Request` and `express.Response` types
to your parameters and you'll see what properties exist on them inline:

## Custom Status Codes
Like any Express server, you can respond with
[custom headers](https://expressjs.com/en/api.html#res.set), and custom status
codes.
```ts title="Example" val
// Visit: https://api.val.town/v1/express/vtdocs.customStatusCode
export const customStatusCode = (
req: express.Request,
res: express.Response
) => {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418
res.status(418).send("I'm a teapot");
};
```
---
# Overview
URL: https://docs.val.town/vals.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
##### What is a val?
- Vals are hosted JavaScript functions
- Vals run on the Val Town serverless platform
- Vals can import other vals, NPM, and more [↗](/reference/import)
- Vals can be public, unlisted, or private [↗](/reference/permissions)
- Vals are run via the Deno runtime [↗](/reference/runtime)
- Vals support JavaScript, TypeScript, JSX, and TSX
There are **4 types of vals**, each with different _triggers_:
---
# Discord welcome bot
URL: https://docs.val.town/integrations/discord/bot.md
import Val from "@components/Val.astro";
You can create a Discord welcome bot using scheduled vals.
In this example, a scheduled val gets the most recent members for your server
and sends a DM to new users from a bot that you'll create. It avoids messaging
users who joined the server before the bot was added.
When users reply to the bot, the message is forwarded as a DM.
## Create a new Discord application
Visit
[https://discord.com/developers/applications](https://discord.com/developers/applications):

## Enable the server members intent
Since the bot will be requesting a list of guild members, you need to enable the
**SERVER MEMBERS INTENT** on the **Bot** tab.

## Get your bot's token
On the **Bot** tab, copy the **TOKEN**, and save it as a [Val Town environment variable](https://www.val.town/settings/environment-variables) as
`discordBot`.

## Create a link via the URL Generator
On the **OAuth2** tab, under **URL Generator,** select the **bot** scope, and
give it permission to **Send Messages**, and then copy the **GENERATED URL** at
the bottom of the page.

## Click on the generated link
Choose the server you want to invite the bot to and press **Continue**.

## Get your Server ID
Save your server id as a [Val Town environment variable](https://www.val.town/settings/environment-variables) as `discordServerId`. (See:
[Where can I find my User/Server/Message ID?](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-))
## Setup your welcome message scheduled val
Fork this scheduled val that will send a welcome message to new users.
This val stores state in three other vals.
- `@me.discordWelcomeBotStartedAt` stores the timestamp of the bot's creation so
that everyone who's already on the server doesn't get a welcome message when
the bot is added.
- `@me.discordWelcomedMembers` stores the ids of everyone that has been messaged
so that they don't receive another message.
- `@me.discordDMs` stores the channel ids (each time the bot DMs a user it
creates a channel) so that we can check for replies to the bot.
These vals will be automatically created if they don't already exist.
## Get your User ID
Save your user id as a [Val Town environment variable](https://www.val.town/settings/environment-variables) as `discordUserId`. (See:
[Where can I find my User/Server/Message ID?](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-))
This is so the bot knows where to forward user replies.
## Set up your message forwarder scheduled val
Fork this scheduled val that will forward user replies to your Discord user.
This val loops through all of the DMs between the bot and new users. It checks
for any new replies and then forwards these messages as a new DM (from the bot,
to your Discord user).
---
# Discord bot
URL: https://docs.val.town/integrations/discord/how-to-make-a-discord-bot-hosted-24-7-for-free-in-.md
import Val from "@components/Val.astro";

## Introduction
By the end of this tutorial you will have a Discord bot hosted 24/7, for free,
forever, on [Val Town](https://val.town), that responds to a basic `/ping`
[Slash Command](https://support.discord.com/hc/en-us/articles/1500000368501-Slash-Commands-FAQ).
[Val Town](https://val.town) is a social website to write, and deploy JavaScript
from the browser.
No prior coding experience required. The vast majority of this tutorial is
creating the bot on Discord and pasting your keys into Val Town. The actual
coding parts are just clicking **Run** two times to fork over two pieces
of code to your Val Town account.
:::note
💡 If you get stuck,
[come join us in the Val Town Discord](https://discord.gg/dHv45uN5RY)!
:::
## Video Tutorial
If you prefer videos, here's this same tutorial in a 3-minute YouTube video.
[https://youtu.be/yYXmInPSSfg](https://youtu.be/yYXmInPSSfg)
## Step 1: Create a Discord app
1. Go to the
[Discord Developer Portal](https://discord.com/developers/applications) and
select "New Application" in the top right

1. Enter a name for your Bot, accept the terms and press Create.

## Step 2: Add the bot to your server
To invite your new bot to your server, you need to generate a link to click on.
1. Head to the **Oauth2** > **URL Generator** page.
2. Select the `bot` scope, then the `Send Messages` permission.

1. **Copy** the URL at the bottom of the page and paste it into a new browser
tab.
2. Click through the steps until you're prompted to choose your server and press
**Continue**.

1. Press **Authorize**.

🎉 Your bot is now a member of your server! Now let's get it to respond to Slash
Commands.
## Step 3: Login to Val Town
[Val Town](https://val.town/) is a social website to write, run, and host
JavaScript. You can create APIs, scheduled functions, email yourself, and
persist small pieces of data — all from the browser and instantly deployed.
There is a generous free plan, and no credit card required.
1. [Login](https://val.town/auth/signin) or
[create an account](https://www.val.town/auth/signup).
## Step 4: Save bot secrets
1. Go to the "General Information" page

1. **Copy** your Application ID
2. Go to [val.town/settings/environment-variables](https://www.val.town/settings/environment-variables)
3. Create a new environment variable called `discordAppId` and paste in the value
4. **Copy** your Public Key, saving it as `discordPublicKey`
5. Go to the Bot page
6. Click **Reset Token** and accept the warning

1. **Copy** and paste it in
[val.town/settings/environment-variables](https://www.val.town/settings/environment-variables) as
`discordBotToken`.
## Step 5: Register a new Slash Command
1. The embedded code below should have your name in the top-left corner. If you
see **anonymous**, refresh this page. If you
still see **anonymous**, ensure
[you're logged into Val Town](https://www.val.town/auth/signin).
2. Press **Run** to add the `/ping` command to your bot.
```ts title="Register /ping command" val
import { registerDiscordSlashCommand } from "https://esm.town/v/neverstew/registerDiscordSlashCommand";
const result = await registerDiscordSlashCommand(
Deno.env.get("discordAppId"),
Deno.env.get("discordBotToken"),
{
name: "ping",
description: "Say hi to your bot",
},
);
export const registerDiscordCommand = result.json();
```
## Step 6: Listen for Slash Commands
1. Press **Run** to listen for Slash Commands and reply.
2. Next to your val's name, click 🔒 > **Unlisted.**
3. In the val above, in the bottom-left corner of the val, click **Script** and change it to **HTTP.**
4. Click on the URL at the bottom of that val that looks like
[https://username-handleDiscordInteraction.web.val.run](https://stevekrouse-handlediscordinteraction.web.val.run/)
5. Visit the General Information page for your bot and paste your copied
endpoint into **INTERACTIONS ENDPOINT URL** and click **Save
Changes**.

## 💬 Try it out
Refresh Discord and try out your bot using `/ping`

## Further Directions
Now that you have a basic Discord Bot setup, you'll want to customize it to do
exactly what you want. You'll probably want to:
1. Register a new Slash Command
2. Connect your bot to APIs like OpenAI's GPT or Dall-E
[Come join us in the Val Town Discord](https://discord.gg/dHv45uN5RY) if you get
stuck, have questions, or want to show off your cool new bot!
---
# Send Discord message via webhook
URL: https://docs.val.town/integrations/discord/send-message.md
import Val from "@components/Val.astro";
1. Create a Discord webhook
2. Add the webhook to your [Val Town environment variables](https://www.val.town/settings/environment-variables)
3. Use `@stevekrouse.discordWebhook` to send the message

```ts title="Discord webhook" val
import { fetchText } from "https://esm.town/v/stevekrouse/fetchText?v=5"; // pin to proxied fetch
export const discordWebhook = async ({
url,
content,
}: {
url: string;
content: string;
}) => {
const text = await fetchText(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ content }),
});
if (text.length) throw Error("Discord Webhook error: " + text);
};
```
You can [browse example usages of this function here](https://www.val.town/v/stevekrouse.discordWebhook/references).
## Example Integration

---
# Slack bot
URL: https://docs.val.town/integrations/slack/bot.md
import Val from "@components/Val.astro";
You can build a Slack bot using vals.
Vals can receive events from Slack's
[Events API](https://api.slack.com/apis/connections/events-api) via the Express
API. In this guide, you'll build a bot that replies whenever it's mentioned.

## 1. Create a Slack app
Visit
[https://api.slack.com/apps?new_app=1](https://api.slack.com/apps?new_app=1),
create a new app **From Scratch**, and choose your **App Name** and your
workspace.

## 2. Save your app's verification token
When your handler val receives requests, you can verify that they were sent by
Slack by looking for the verification token.
To find your app's verification token, go to **Settings** → **Basic
Information** in the side bar. Scroll down to **App Credentials**.

Save the **Verification Token** as a
[Val Town environment variable](https://www.val.town/settings/environment-variables) as
`slackVerificationToken`.
## 3. Create a val that will respond to the challenge and reply when it's mentioned
When you add a **Request URL**, Slack will immediately send a challenge to
verify the endpoint is correct.
Run this val that responds to the challenge, and replies to `app_mention`
events.
## 4. Set up event subscriptions
On your forked val, copy the Web endpoint via the menu at Endpoints > Copy web
endpoint.
Back on Slack, navigate to the **Event Subscription** page. It's under
**Features** in the side bar.
Toggle on **Enable Events**, and paste your endpoint in the **Request URL**
field.

A few seconds later, you should see a verified message above the input box.
## 4. Subscribe to the [app_mention](https://api.slack.com/events/app_mention) event
On the **Event Subscriptions** page, scroll down to the **Subscribe to bot
events** section, and subscribe to the
[app_mention](https://api.slack.com/events/app_mention) event. Make sure to
**Save Changes**.

## 5. Add the necessary scopes to your app
In **Features** → **OAuth & Permissions**, scroll down to **Scopes** and enable
the scopes you need.
So the bot can reply when it's mentioned, add `app_mentions:read` and
`chat:write`.

## 6. Install your app to your workspace
In **Settings** → **Install App**, install the app to your workspace.

## 7. Get your app's OAuth token
In **Features** → **OAuth & Permissions**, copy the **Bot User OAuth Token**,
and save it as a [Val Town environment variable](https://www.val.town/settings/environment-variables) as
`slackToken`.

## 7. Add your bot to the relevant Slack channel
The bot will receive an event when it's mentioned in a channel that it's been
invited to.
Invite the bot to your channel.

Mention the bot, and the val you forked earlier
([@vtdocs.slackReplyToMessage](https://www.val.town/v/vtdocs.slackReplyToMessage))
will reply to the message!

Vals not behaving like you'd expect? Get help on the
[Val Town Discord](https://discord.gg/dHv45uN5RY).
---
# Proxied fetch
URL: https://docs.val.town/std/fetch.md
The Javascript [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is directly available within a Val. However sometimes fetch calls are blocked by the receiving server for using particular IP addresses. Additionally, network blips or unreliable web services may lead to failures if not handled properly.
The Val Town standard library contains an alternative version, [`std/fetch`](https://www.val.town/v/std/fetch), that wraps the JavaScript Fetch API to provide additional functionality. The fetch function from [`std/fetch`](https://www.val.town/v/std/fetch) reroutes requests using a proxy vendor so that requests obtain different IP addresses. It also automatically retries failed requests several times. Note that using [`std/fetch`](https://www.val.town/v/std/fetch) will be significantly slower than directly calling the Javascript Fetch API due to extra network hops.
## Usage
After importing [`std/fetch`](https://www.val.town/v/std/fetch), the fetch method is used with the same signature as the Javascript Fetch API.
```ts title="Example" val
import { fetch } from "https://esm.town/v/std/fetch";
let result = await fetch("https://api64.ipify.org?format=json");
let json = await result.json();
console.log(json.ip);
```
If you run the above code multiple times, you'll see that it returns different IP addresses, because [`std/fetch`](https://www.val.town/v/std/fetch) uses proxies so that each request is made from a different IP address.
---
# Exports
URL: https://docs.val.town/troubleshooting/exports.md
Vals with triggers (HTTP, Cron, Email) require at least one export: the function to run when that val is triggered. If your val has multiple exports, then we require one of them to the `default` export, which will be the function that runs when the val is triggered.
Script vals can export anything or nothing.
```ts title="Example" val
/**
* Note the 'export' keyword
*/
export function handleEmail(email: Email) {
console.log("ok!");
}
```
:::note[Export syntax]
Val Town supports modern [JavaScript module syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#exporting_module_features).
Val Town does not support CommonJS `module.exports` and `require()`.
:::
---
# Script
URL: https://docs.val.town/vals/script.md
Script vals are freeform JavaScript, TypeScript, or JSX. They are useful
for testing, saving utility functions, and one-off operations.
It's common to use script vals to write standalone functions that can
be imported by your HTTP, Cron, and Email vals. A script val
can be as simple as a function that just adds two numbers:
```tsx val
export function add(a: number, b: number) {
return a + b;
}
```
Script vals can also export static values: they don't need to export
functions.
```tsx
export const MEANING_OF_LIFE = 42;
```
---
# Upgrade Guide: Safer Val Scopes
URL: https://docs.val.town/troubleshooting/std-set-permission-error.md
On Nov 1, 2024, [we announced safer default API scopes for vals](https://blog.val.town/blog/api-token-scopes/), which don't include the `val:write` scope. Some users were not included in that upgrade, because we detected it might cause some of their vals to break. We reached out to those users on Jan 10, 2025 to give them a month to upgrade their vals. We removed the `val:write` scope from all those remaining users vals on Feb 11, 2025 unless we heard back from you.
One of the most common uses of the `val:write` permission is the now-deprecated [std.set](https://www.val.town/v/std/set), since it modifies another val. If you're seeing an error with `std/set`, there are two actions you can take, outlined below.
As always, feel free to reach out in [#general](https://discord.com/channels/1020432421243592714/1020432421243592717) on Discord or [via email](/contact-us/contact-us/) if you have any questions or want to set up a time to pair program.
## Use Blob Storage Instead (recommended)
`Std/set` is deprecated and we now use blob storage with our [`std/blob`](https://www.val.town/v/std/blob) library instead. The equivalent function to `std.set()` is [`blob.setJSON()`](https://www.val.town/v/std/blob#L161).
```js
// Using std/set
import { set } from "https://esm.town/v/std/set";
set("createdAt", Date.now());
```
```js
// Using std/blob
import { blob } from "https://esm.town/v/std/blob";
blob.setJSON("createdAt", Date.now());
```
Below is a real world example of swapping out `std/set` for `std/blob` in [a cron val](https://www.val.town/v/stevekrouse/pollRSSFeeds2) that polls RSS feeds and emails the results:

If you want to learn more about how to use blob storage, [check out our docs here](https://docs.val.town/std/blob/#_top). You can also view and manually edit your blobs by forking this [blob admin val](https://www.val.town/v/stevekrouse/blob_admin).
## Add Val Write Permissions to Your Val
Alternatively, you can update your val's permission scopes so it can create or modify other vals again. To do this:
### 1. Go to your val's Settings

### 2. Scroll down to the Permissions section and select the Vals dropdown

### 3. Select Read and Write

You're good to go!
---
# Send messages to Slack
URL: https://docs.val.town/integrations/slack/send-messages-to-slack.md
You can send messages to Slack from vals.
In this guide you'll create an incoming webhook for your Slack workspace, and
send a request to it with a message payload.

## Create a Slack app
Visit
[https://api.slack.com/apps?new_app=1](https://api.slack.com/apps?new_app=1),
create a new app **From Scratch**, and choose your **App Name** and your
workspace.

## Go to the incoming webhooks page
On the next page (or in the side bar), click **Incoming Webhooks**.

## Create an incoming webhook
Click the **Activate Incoming Webhooks** toggle.

Scroll down, and click **Add New Webhook to Workspace**.

Select the channel which the webhook will send messages to.

## Save the webhook address as a secret
You'll be taken back to the **Incoming Webhooks** page (if not, you can find
it via the side bar).

Copy the **Webhook URL** for the webhook you just created and save it as a
[Val Town environment variable](https://www.val.town/settings/environment-variables) as `slackWebhookURL`.
## Use [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to send a message
Translate the sample cURL request to a [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) request.
You can run the following val:
```ts title="Send message" val
const res = await fetch(Deno.env.get("slackWebhookURL"), {
method: "POST",
body: JSON.stringify({
text: `Hello, ~World~ from Val Town!`,
}),
});
console.log(res.statusText);
```
If it worked, you will get a message in the channel you configured earlier!

Ran into a problem? Get help on the
[Val Town Discord](https://discord.gg/dHv45uN5RY).
---
# Neon Postgres
URL: https://docs.val.town/integrations/databases/neon-postgres.md
import Val from "@components/Val.astro";
Neon provides a PostgreSQL database with an API accessible over HTTP
and a JavaScript SDK. This lets you use a more
conventional relational database model in your vals.
## Setup
Login to your Neon account, then click Create a project:

Fill out the fields:
- Name: Any.
- Postgres version: If you need to use the database with other software, 14 provides better compatibility. Else, use the default of 15.
- Region: Choose any US region for lower latency.
Your settings should look similar to the following:

You should then get a dialog with connection details. Select the PostgreSQL connection URL highlighted in green without the quotation marks around it, then copy it:

Go to [val.town](https://val.town), click on your profile picture and click **Env Variables**.

Click **New env variable**, then set the key to a name you'd like to use to reference the environment variable and the value to the connection URL you just copied:

You can then use deno-postgres to connect to the database like so:
```ts title="Example" val
import { Client } from "https://deno.land/x/postgres/mod.ts";
const client = new Client(Deno.env.get("neon_url"));
await client.connect();
const result = await client.queryObject`select CURRENT_TIME;`;
console.log(result);
```
Note that you might need to change the environment variable name according to the key you set earlier.
While writing your code, remember that you should use prepared statements for any queries containing user data (or any query that has to change with new data) to prevent SQL injection attacks. You can find the 3 ways of going about this at [https://deno-postgres.com/#/?id=prepared-statements-and-query-arguments](https://deno-postgres.com/#/?id=prepared-statements-and-query-arguments).
---
# PlanetScale
URL: https://docs.val.town/integrations/databases/planetscale.md
import Val from "@components/Val.astro";
[PlanetScale](https://planetscale.com/) once provided a hosted MySQL database with 5GB
of storage included in the free tier. On April 8, 2024, the free plan was retired and the [cheapest plan](https://planetscale.com/pricing) starts at $39 with 10GB storage.
## 1. Sign up to PlanetScale
Go to
[https://auth.planetscale.com/sign-up](https://auth.planetscale.com/sign-up)
## 2. Create a new database
Select **New database** on the dashboard.
Create a database.


## 3. Create a password
Go to **Settings** → **Passwords** and click **New password**.

Save the `host`, `username`, and `password` values as
[Val Town environment variables](https://www.val.town/settings/environment-variables) - use
`planetScaleHost`, `planetScaleUsername`, and `planetScalePassword`
respectively.

## 4. Create your first table
Copy and paste this val to create a table with the given schema.
```ts title="Table creation" val
import { queryPlanetScale } from "https://esm.town/v/vtdocs/queryPlanetScale";
await queryPlanetScale(
{
host: Deno.env.get("planetScaleHost"),
username: Deno.env.get("planetScaleUsername"),
password: Deno.env.get("planetScalePassword"),
},
`CREATE TABLE stock (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(255) NOT NULL,
price varchar(255) NOT NULL
);`
);
```
## 5. Insert an item
Insert one or more items.
```ts title="Data insertion" val
import { queryPlanetScale } from "https://esm.town/v/vtdocs/queryPlanetScale";
await queryPlanetScale(
{
host: Deno.env.get("planetScaleHost"),
username: Deno.env.get("planetScaleUsername"),
password: Deno.env.get("planetScalePassword"),
},
`INSERT INTO stock (name, price) VALUES (?, ?);`,
["banana", 15]
);
```
## 6. Query an item
Use the `rows` property to get your query results - or log the entire
`results` object to see what other data is available.
```ts title="Query stock table" val
import { queryPlanetScale } from "https://esm.town/v/vtdocs/queryPlanetScale";
const results = await queryPlanetScale(
{
host: Deno.env.get("planetScaleHost"),
username: Deno.env.get("planetScaleUsername"),
password: Deno.env.get("planetScalePassword"),
},
`SELECT id, name, price FROM stock WHERE name=?`,
["banana"]
);
console.log(results.rows[0]);
```
## 7. Do more than queries!
Read the source of the
[@vtdocs/queryPlanetScale](https://www.val.town/v/vtdocs/queryPlanetScale)
helper, see how it uses the `@planetscale/database` SDK, refer to the
[driver's documentation](https://github.com/planetscale/database-js), and extend
it!
---
# Supabase
URL: https://docs.val.town/integrations/databases/supabase.md
[Supabase](https://supabase.com/) provide a hosted Postgres database with 500MB
of storage in the [free tier](https://supabase.com/pricing).
You can query and create data inside your vals using either Supabase's
JavaScript SDK, or a Postgres client.
## Using the SDK
### 1. Sign up to Supabase
Visit
[https://supabase.com/dashboard/sign-up](https://supabase.com/dashboard/sign-up).
### 2. Create a project
On Supabase's [dashboard](https://supabase.com/dashboard/projects), create a new
project.

### 3. Get your API URL and service role key
Go to your project's **Settings** via the sidebar. Inside **API**, scroll down
and copy the **Project URL,** and then, inside **Project API Keys**, copy the
**service role** secret. Save these as two separate
[Val Town environment variables](https://www.val.town/settings/environment-variables) as `supabaseURL` and
`supabaseKey` respectively.

### 4. Create your first table
Head to the **Table editor** in the sidebar.

**Create a new table** called `my_first_table` with the following schema.

### 5. Insert some data
Copy and paste the following val to insert some data.
```ts title="Data insertion" val
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const supabase = createClient(
Deno.env.get("supabaseURL"),
Deno.env.get("supabaseKey")
);
const { data, error } = await supabase
.from("my_first_table")
.insert({ name: "Alice", data: { job: "software engineer" } });
console.log(data, error);
```
### 6. Query back that data
Get back the data you just inserted by using `eq()` (like SQL's `WHERE`).
```ts title="Data query" val
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const supabase = createClient(
Deno.env.get("supabaseURL"),
Deno.env.get("supabaseKey")
);
const { data, error } = await supabase
.from("my_first_table")
.select("name, data")
.eq("name", "Alice")
.limit(1);
if (error) {
throw error;
}
console.log(data);
```
### 7. Keep going!
Use Supabase's
[JavaScript Client library documentation](https://supabase.com/docs/reference/javascript/select)
to write more queries!
## Using a Postgres client
### 1. Sign up to Supabase
Visit
[https://supabase.com/dashboard/sign-up](https://supabase.com/dashboard/sign-up).
### 2. Create a project
On Supabase's [dashboard](https://supabase.com/dashboard/projects), create a new
project.

Keep a note of your database password, you'll need this to create a database
connection string.

### 3. Get your database's connection string

Go to your project's **Settings** via the sidebar. Inside **Database**, scroll
down and copy the **Connection string** for **Nodejs**. Replace
`[YOUR-PASSWORD]` (removing the square brackets) with your database password
(alternatively, reset your database password to create a new one).
Save this connection string as a
[Val Town environment variable](https://www.val.town/settings/environment-variables) as `supabasePostgres`.
### 4. Create your first table
Copy and paste this val to create a table with the given schema.
```ts title="Table creation" val
import { supaBaseQuery } from "https://esm.town/v/vtdocs/supaBaseQuery";
await supaBaseQuery(
Deno.env.get("supabasePostgres"),
`create table my_first_table (
id bigint generated by default as identity primary key,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null,
updated_at timestamp with time zone default timezone('utc'::text, now()) not null,
data jsonb,
name text
);`
);
```
### 5. Insert some data
Use a [prepared statement](https://deno-postgres.com/#/?id=prepared-statements-and-query-arguments)
like below to prevent [SQL injection](https://en.wikipedia.org/wiki/SQL_injection).
```ts title="Data insertion" val
import { supaBaseQuery } from "https://esm.town/v/vtdocs/supaBaseQuery";
await supaBaseQuery(
Deno.env.get("supabasePostgres"),
`INSERT INTO MY_FIRST_TABLE (NAME, DATA) VALUES ($1, $2);`,
["Alice", '{"job": "software engineer"}']
);
```
### 6. Query back that data
Usually, you'll just want the `rows` property from the response.
```ts title="Data query" val
import { supaBaseQuery } from "https://esm.town/v/vtdocs/supaBaseQuery";
const data = await supaBaseQuery(
Deno.env.get("supabasePostgres"),
`SELECT NAME, DATA FROM MY_FIRST_TABLE WHERE NAME = $1;`,
["Alice"]
);
console.log(data);
```
### 7. More resources
Learn more about the [Deno Postgres client](https://deno-postgres.com/) used in
this guide, view the Supabase
[Database documentation](https://supabase.com/docs/guides/database), or get help
on [Val Town's Discord](https://discord.gg/dHv45uN5RY).
---
# Get a Github user
URL: https://docs.val.town/integrations/github/get-a-github-user.md
For example, we can get a user without passing an authentication token.

GitHub API endpoints have a cURL command that you can manually translate into a `fetchJSON` call (e.g. see [Get a user](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user)).
Here's a Val that returns a GitHub user object for a given username:
```ts title="@vtdocs/getGithubUser" val
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
export const getGithubUser = async (username: string) => {
const user = await fetchJSON(
`https://api.github.com/users/${username}`,
);
return user;
};
```
You can alter it, or just call it directly like this:
```ts title="Usage of @vtdocs/getGithubUser" val
import { getGithubUser } from "https://esm.town/v/vtdocs/getGithubUser";
console.log(await getGithubUser("stevekrouse"));
```
You can also do this with [octokit.js](https://github.com/octokit/octokit.js). Reminder: import npm packages by prepending the package name with `npm:`.
```ts title="Usage of npm:@octokit/core" val
import { Octokit } from "npm:@octokit/core";
export const getGithubUserViaOctokit = async (username: string) => {
const octokit = new Octokit();
const user = await octokit.request("GET /users/{username}", {
username,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
return user;
};
```
---
# Upstash
URL: https://docs.val.town/integrations/databases/upstash.md
import Val from "@components/Val.astro";
Upstash provides a serverless Redis database, which can be used as a key-value
store of up to 1mb with a free account.
## Create an Upstash account
Go to [https://console.upstash.com/login](https://console.upstash.com/login)
## Create a database
1. Click **Create database**

1. **Name**: whatever you want.
2. **Type**: Regional
3. **Region**: Iowa (us-central1), because it's closest to Val Town's servers.
4. **Enable TLS** for security.

## Add REST credentials to Val Town environment variables
1. Go to [val.town/settings/environment-variables](https://www.val.town/settings/environment-variables)
2. For `UPSTASH_REDIS_REST_URL` and the `UPSTASH_REDIS_REST_TOKEN` each:
1. Click **New env variable**.
2. Set the names to `upstashURL` and `upstashToken`, respectively
3. Copy & paste in the value
4. Click **Add**
Upstash:

Val Town:

## Set some data
If you set it up correctly, you should be able to _Run_ this Val and have
it return the same results from your own Upstash database
```ts title="Saving data" val
import { Redis } from "npm:@upstash/redis";
const redis = new Redis({
url: Deno.env.get("upstashURL"),
token: Deno.env.get("upstashToken"),
});
console.log(await redis.set("foo", "bar"));
console.log(await redis.get("foo"));
```
## Saving JSON
JSON is automatically stringified and parsed so you can set it and get it
directly. You can store a JSON object of up to 1mb this way with a free account.
```ts title="Saving JSON value" val
import { Redis } from "npm:@upstash/redis";
const redis = new Redis({
url: Deno.env.get("upstashURL"),
token: Deno.env.get("upstashToken"),
});
await redis.set("json-ex-1", { a: { b: "nested json" } });
console.log(((await redis.get("json-ex-1")) as any).a.b);
```
## Further resources
1. [Upstash Redis SDK Docs](https://docs.upstash.com/redis/sdks/javascriptsdk/overview)
2. [Redis tutorial](https://redis.io/docs/data-types/tutorial/)
Thanks to [@mattx](https://www.val.town/mattx) for contributions to this
resource!
---
# Github user's stars (pagination)
URL: https://docs.val.town/integrations/github/github-users-stars-pagination.md
When there are many results, GitHub
[paginates](https://docs.github.com/en/rest/guides/using-pagination-in-the-rest-api?apiVersion=2022-11-28#about-pagination)
the data. You can use the `per_page` parameter to loop over the data. Here's a
practical example: getting the total stars across a user's repositories.
Here's a Val that returns the star count for a username.
```ts title="@vtdocs/getGithubStars" val
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
export const getGithubStars = async (username: string) => {
const user = await fetchJSON(
`https://api.github.com/users/${username}`,
);
let totalStars = 0;
// Paginate the max number of pages per request
let pages = Math.ceil(user.public_repos / 100);
while (pages--) {
for (
const repo of await fetchJSON(
`https://api.github.com/users/${username}/repos?per_page=100&page=${
pages + 1
}`,
)
) {
totalStars += repo.stargazers_count;
}
}
return totalStars;
};
```
Call it to get the result:
```ts title="Usage of @vtdocs/getGithubStars" val
import { getGithubStars } from "https://esm.town/v/vtdocs/getGithubStars";
console.log(await getGithubStars("stevekrouse"));
```
## Email yourself when you get a comment reaction!
Create a GitHub personal access token:
[https://github.com/settings/personal-access-tokens/new](https://github.com/settings/personal-access-tokens/new).
Give it permissions to the repositories that you're interested in getting
comment reaction notifications for.

Allow _Read-only_ access for issues to find your recent issue comments (pull
requests are issues).

Go to:
[https://www.val.town/settings/environment-variables](https://www.val.town/settings/environment-variables)
and save the token as `githubPatToken`.
Run this Val, change `username` to your GitHub username, and schedule it to run
every **fifteen minutes**.
```ts title="Every 15 minutes" val
import { email } from "https://esm.town/v/std/email?v=9";
import { msMinute } from "https://esm.town/v/stevekrouse/msMinute?v=1";
import { msAgo } from "https://esm.town/v/rodrigotello/msAgo?v=2";
import { githubPatToken } from "https://esm.town/v/vtdocs/githubPatToken";
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
export const emailGithubReactions = async () => {
const username = "stevekrouse";
const events = await fetchJSON(
`https://api.github.com/users/${username}/events?per_page=100`,
{
headers: {
Authorization: `Bearer ${githubPatToken}`,
},
},
);
const comments = events.filter((event) =>
event.type === "IssueCommentEvent" && event.payload.action === "created"
);
const recentReactions = [];
for (const comment of comments) {
for (
const reaction of await fetchJSON(
comment.payload.comment.reactions.url,
)
) {
if (msAgo(15 * msMinute)) {
recentReactions.push(
`${reaction.user.login} reacted with ${reaction.content} to ${comment.payload.comment.html_url}`,
);
}
}
}
if (recentReactions.length > 0) {
await email({
text: recentReactions.join("\n"),
subject: "new github reactions!",
});
}
};
```
When this Val runs, it gets the last 100 events for an authenticated user. It
makes additional requests to get the reactions for any recent events that are
comments.
If the reaction was created in the last fifteen minutes, then it's a new
comment!
You'll receive a batch of reactions and comment links only for new reactions.
---
# Receiving a GitHub Webhook
URL: https://docs.val.town/integrations/github/receiving-a-github-webhook.md
import Val from "@components/Val.astro";
GitHub supports sending webhooks for a
[number of events](https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads).
In this example, you'll use [@std/email](/std/email/) to send yourself an email when someone
stars your GitHub repository, but you could also send a notification to
[Slack](/integrations/slack/send-messages-to-slack/),
[Discord](/integrations/discord/send-message/), or anywhere else.
First, create an HTTP val to receive star webhooks from
GitHub, or use this one:
Go to the **Webhooks** tab in your GitHub repository's settings.
Get your val's web endpoint URL (via the menu) and paste it in the **Payload
URL.**

Be sure to set the content type to `application/json` so that it's super easy
for us to extract the data out of it.

You can filter for the specific events you want to receive, or opt to choose
everything.
To just star events, toggle the checkbox inside _Let me select individual
events._

When your GitHub repository is starred, GitHub will sends a webhook payload,
which is handled by your val, and you get an email!
You can debug this on the GitHub side by looking at the Recent Deliveries page
of your webhook.

## Securing GitHub Webhooks
Once public, your val function will listen for any payload sent to its endpoint.
For security reasons, you probably want to limit requests to those coming from
GitHub. One method is to add a secret token and perform validation.
Edit your existing webhook on GitHub and in the **Secret** field, add a random
string with high entropy. Add this to your
[environment variables](/reference/environment-variables/) on Val Town as `githubWebhookToken`.

GitHub uses this secret to create a hash signature with each payload. This hash
signature is sent as a header: `x-hub-signature-256`.
With your secret, the payload, and the signature header, you can use Octokit's
[verify](https://github.com/octokit/webhooks.js/#webhooksverify) function to
confirm that the webhook came from GitHub. Here's how you would integrate that into the original example:
With your webhook pointing to the new example, your val now refuses payload that
don't come from GitHub!
If you want more details, GitHub has an in-depth guide on
[securing webhooks](https://docs.github.com/en/webhooks-and-events/webhooks/securing-your-webhooks).
---
# SQLite
URL: https://docs.val.town/std/sqlite.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
[SQLite](https://www.sqlite.org/) is a lightweight, standard database. Every Val Town account comes with its own private SQLite database that is accessible from any of your vals via [`std/sqlite`](https://www.val.town/v/std/sqlite).
Val Town SQLite is powered by [Turso](https://turso.tech/).
### SQLite Admin Panels
We recommend these admin data viewers for managing your database – viewing or editing data or your database table schema:
- [Outerbase Studio](https://libsqlstudio.com/) **(recommended)** - formerly LibSQL Studio – see [instructions](https://libsqlstudio.com/docs/connect-valtown)
- [SQLite Explorer](https://www.val.town/v/nbbaier/sqliteExplorerApp) – built in a val – easy to customize in Val Town!
### Limits
You can store 10mb on the free plan and up to 1gb on the paid plan. Contact us if you need more space.
---
# ORMs
URL: https://docs.val.town/std/sqlite/orms.md
Writing SQL is really fast and it's great for small projects. As your projects
grow, you might want to take advantage of an ORM or query builder instead. Here
are a few examples of how to do that with popular tools.
## Drizzle
Here's how to use the awesome [Drizzle ORM](https://orm.drizzle.team/)
module with Val Town and SQLite. It's great because it allows you to write
very expressive SQL. We even use Drizzle to build Val Town!
```ts title="Example" val
import { sqlite } from "https://esm.town/v/std/sqlite";
import { sql } from "npm:drizzle-orm";
import { drizzle } from "npm:drizzle-orm/libsql";
import { integer, sqliteTable, text } from "npm:drizzle-orm/sqlite-core";
const db = drizzle(sqlite as any);
const kv = sqliteTable("kv", {
key: text("key").primaryKey(),
value: text("value").notNull(),
});
const sqliteDrizzleExample = await db.select().from(kv).all();
console.log(sqliteDrizzleExample);
```
## Prisma
🚫 Prisma isn't supported in Val Town because it relies on functionality that
only exists in a classic server environment.
## Sequelize
🚫 Sequelize isn't supported in Val Town because it relies on specific database
drivers and is not extensible.
---
# Migrations
URL: https://docs.val.town/std/sqlite/migrations.md
One way to run migrations for SQLite in Val Town is to write a single val and keep
updating it with each migration. The version history of that val can act as a log of schema changes.
Let's look at an example: In our first migration, we create a users table:
```ts title="Migration 1" val
import { sqlite } from "https://esm.town/v/std/sqlite";
await sqlite.execute(`
create table users (
id integer primary key autoincrement,
email text not null unique
)
`);
```
Later, we realize we also want to store names. We can update _the same val_ to alter
the table:
```ts title="Migration 2" val
import { sqlite } from "https://esm.town/v/std/sqlite";
await sqlite.execute(`
alter table users
add column name text
`);
```
---
# Usage
URL: https://docs.val.town/std/sqlite/usage.md
The Val Town [SQLite val](https://www.val.town/v/std/sqlite) has two methods: `execute` [↗](https://docs.turso.tech/sdk/ts/reference#simple-query) and `batch` [↗](https://docs.turso.tech/sdk/ts/reference#batch-transactions). Below are examples of how to use them in Val Town.
## Simple query
```ts title="Simple query" val
import { sqlite } from "https://esm.town/v/std/sqlite";
const data = await sqlite.execute("SELECT datetime();");
console.log(data.rows[0]);
```
## Basic usage
```ts title="Basic usage" val
import { sqlite } from "https://esm.town/v/std/sqlite";
await sqlite.execute(`create table if not exists kv(
key text unique,
value text
)`);
const key = crypto.randomUUID();
await sqlite.execute({ sql: `insert into kv(key, value) values(?, ?)`, args: [key, "value1"] });
const result = await sqlite.execute({ sql: `select * from kv where key = ?`, args: [key] });
console.log(result);
// {
// columns: [ "key", "value" ],
// columnTypes: [ "TEXT", "TEXT" ],
// rows: [ [ "d65991f8-6f03-4275-bcf1-1fdb1164e153", "value1" ] ],
// rowsAffected: 0,
// lastInsertRowid: null
// }
const rows: { key: string; value: string }[] = result.rows.map(row =>
Object.fromEntries(row.map((value, index) => [result.columns[index], value])) as any
);
console.log(rows); // [ { key: "d65991f8-6f03-4275-bcf1-1fdb1164e153", value: "value1" } ]
```
## Create a table
```ts title="Table creation" val
import { sqlite } from "https://esm.town/v/std/sqlite";
await sqlite.execute(`create table if not exists kv(
key text unique,
value text
)`);
```
## Get data
```ts title="Data query" val
import { sqlite } from "https://esm.town/v/std/sqlite";
console.log(await sqlite.execute(`select key, value from kv`));
```
## Insert data
```ts title="Data insertion" val
import { sqlite } from "https://esm.town/v/std/sqlite";
await sqlite.execute({
sql: `insert into kv(key, value) values (:key, :value)`,
args: { key: "specialkey", value: "specialvalue" },
});
```
## Delete data
```ts title="Data deletion" val
import { sqlite } from "https://esm.town/v/std/sqlite";
await sqlite.execute({
sql: `delete from kv where key = :key`,
args: { key: "specialkey" },
});
```
## Batch queries
```ts title="Batching queries" val
import { sqlite } from "https://esm.town/v/std/sqlite";
const charge = 10;
export const batchSqlite = await sqlite.batch([
`create table if not exists accounts(person_id text unique, balance integer)`,
{
sql: `update accounts set balance = balance - :charge where person_id = 'Bob'`,
args: { charge },
},
{
sql: `update accounts set balance = balance + :charge where person_id = 'Alice'`,
args: { charge },
},
]);
```
---
# Permission errors
URL: https://docs.val.town/troubleshooting/permission-errors.md
Some NPM and Deno modules will produce an error like this:
```json
{
"error": "Requires read access to , run again with the --allow-read flag"
}
```
Vals run in a sandbox with limited access: we use
[Deno's permissions system](https://docs.deno.com/runtime/manual/basics/permissions)
to limit access to anything that could allow one val to read data from another,
or otherwise compromise security.
NPM modules, on the other hand, often do unexpected and dangerous things, like
downloading other code at runtime, accessing your environment variables, reading
files from disk, and more. This unrestricted access is the root cause of many
security issues for Node.js.
We have expanded our permissions to support as many things as possible while
still maintaining good isolation. If you come across a package that you think
should be supported please let us know.
## Allowed permissions
Vals have the following permissions:
1. Network access (`--allow-net`).
2. Environment access, limited to your secrets (`--allow-env`).
3. Some system information (`--allow-sys=osRelease,homedir,cpus,hostname`) This
allows you to access the following functionality:
```ts
import os from "node:os";
os.cpus();
os.homedir();
Deno.osRelease();
Deno.hostname();
```
These values are not particularly useful, but are sometimes relied upon by
certain libraries.
4. Read access to some files, including the NPM registy cache and the current
working directory. This allows you to read the
[lockfile](/reference/version-control/#lockfiles) contents
(`console.log(Deno.readTextFileSync("deno.lock"));`) and read files imported
by NPM modules.
Vals do not have access to:
1. General filesystem read/write operations (`--allow-read` and
`--allow-write`), except for the specific cases mentioned above.
2. Dynamic libraries (`--allow-ffi`).
3. Extended system information beyond the permitted values (`--allow-sys`).
4. High-resolution time measurement (`--allow-hrtime`).
5. Subprocess or executable execution (`--allow-run`).
## Workarounds
### Use esm.sh
If you're having problems using an NPM module because of permission issues, you
can often import the package from [esm.sh](https://esm.sh/) instead of NPM to
quickly resolve the issue. For example:
```diff lang="ts"
- import garminConnect from "npm:garmin-connect";
+ import garminConnect from "https://esm.sh/garmin-connect";
```
esm.sh transpiles code in order to run cleanly on Deno.
---
# Email
URL: https://docs.val.town/std/email.md
Send emails with [`std/email`](https://www.val.town/v/std/email). You can only send emails to yourself if you're on Val Town Free. If you're on [Val Town Pro](https://www.val.town/pricing), you can email anyone.
:::note
Want to receive emails instead? [Create an email handler val](/types/email/)
:::
## Basic usage
```ts title="Example" val
import { email } from "https://esm.town/v/std/email";
await email({ subject: "New Ink & Switch Post!", text: "https://www.inkandswitch.com/embark/" });
```
### `subject`
The email subject line. It defaults to `Message from @your_username on Val Town`.
### `to`, `cc`, and `bcc`
By default, the `to` field is set to the owner of the Val Town account that calls it.
If you have Val Town Pro, you can send emails to anyone via the `to`, `cc`, and `bcc` fields.
If you don't have Val Town Pro, you can only send emails to yourself, so leave those fields blank.
### `from`
The `from` is limited to a few options:
1. It defaults to `notifications@val.town` if you don't specify it.
2. If you do specify it, it must be of the form:
- `{ email: "your_username.valname@valtown.email }"` or
- `{ email: "your_username.valname@valtown.email", name: "Sender Name" }`.
### `replyTo`
`replyTo` accepts a string email or an object with strings for `email` and `name` (optional).
This can be useful if you are sending emails to others with Val Town Pro.
```ts title="replyTo" val
import { email } from "https://esm.town/v/std/email";
await email({
to: "someone_else@example.com",
from: "your_username.valname@valtown.email",
replyTo: "your_email@example.com",
text: "these pretzels are making me thirsty",
});
```
### Attachments
You can attach files to your emails by using the `attachments` field.
Attachments need to be [Base64](https://en.wikipedia.org/wiki/Base64) encoded,
which is what the [btoa](https://developer.mozilla.org/en-US/docs/Web/API/btoa)
method is doing in this example:
```ts title="Attachments example" val
import { email } from "https://esm.town/v/std/email";
export const stdEmailAttachmentExample = email({
attachments: [
{
content: btoa("hello attachments!"),
filename: "test.txt",
type: "text",
disposition: "attachment",
},
],
});
```
Here's an example sending a [PDF](https://www.val.town/v/stevekrouse/sendPDF).
### Headers
You can set custom headers in emails that you send:
```ts title="Custom headers example" val
import { email } from "https://esm.town/v/std/email?v=13"
console.log(
await email({
text: "Hi",
headers: {
"X-Custom-Header": "xxx",
},
}),
)
```
This is also [documented in our REST API](https://docs.val.town/openapi#/tag/emails/POST/v1/email), and supported in the [SDK](https://docs.val.town/api/sdk/).
---
# Express to HTTP migration
URL: https://docs.val.town/troubleshooting/express-to-http-migration.md
The deprecated Express type of Val is being replaced by HTTP on October 1, 2024. If you still have Express vals running and want to upgrade them, here are some tips for doing so. [Reach out to us if you need help with the process](/contact-us/contact-us/).
## Parameters
- Express vals took two arguments: a request and a response, and their return value was ignored.
- HTTP vals receive a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) as an argument and they need to return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object to reply.
Here are equivalent vals for the express versus HTTP types:
```ts
// Express handler
export function handler(req, res) {
res.send("Hello world");
}
```
```ts
// HTTP handler
export function handler(req) {
return new Response("Hello world");
}
```
## The response object
Where with express vals, you set the response status, headers, and other details by chaining functions off of the response object, with the HTTP type, these are options you set for the Response object.
```ts
// Express handler
export function handler(req, res) {
res.status(200).set("Content-Type", "text/plain").send("Hello world");
}
```
```ts
// HTTP handler
export function handler(req) {
return new Response("Hello world", {
status: 200,
headers: {
"Content-Type": "text/plain",
},
});
}
```
## The request object
The request object for the express handler looks like [an express Request object, which is based on a Node.js request object](https://expressjs.com/en/4x/api.html#req). The Request object for the HTTP handler is [the web-standard Request object](https://developer.mozilla.org/en-US/docs/Web/API/Request). This means that some things, like getting query string parameters, will require different code:
```ts
// Express handler
export function handler(req, res) {
res.send(req.query.name);
}
```
```ts
// HTTP handler
export function handler(req) {
return new Response(new URL(req.url).searchParams.get("name"));
}
```
## Endpoints
HTTP vals can be accessed by subdomains ending in `web.val.run`, like `https://username-valname.web.val.run`. Express vals are accessed on a different subdomain path, ending in `express.val.run`. You may need to update code and links that point to the val.
---
# Interop with Node, Deno, and Browsers
URL: https://docs.val.town/guides/interop.md
Vals can be written in TypeScript and JavaScript. JavaScript
is the natural language of the web, and benefits from having
many different runtimes.
So you can run your vals outside of Val Town, but because each
of the JavaScript runtimes is a bit different, there are some
notes to be aware of.
### Deno
Val Town's own runtime is built on [Deno](https://deno.land/),
so naturally vals run best on Deno outside of Val Town.
You can reasonably expect Deno to _just work_ with Vals:
you can just copy the **Module URL** from the dropdown
menu in the val editor and plug it into Deno:
```sh
$ deno run https://esm.town/v/tmcw/randomVal
0.5133662021089374
```
**Caveats**: When you run vals locally, you'll need to make sure that
any [environment variables](/reference/environment-variables/)
you've set in Val Town are set as environment variables in your
local environment.
**Private vals** can be run by setting [DENO_AUTH_TOKENS](https://docs.deno.com/runtime/manual/basics/modules/private). Create an [API token](https://docs.val.town/api/authentication/)
in Val Town, and then use it for the esm.town domain:
```sh
$ DENO_AUTH_TOKENS=xxx@esm.town deno run https://esm.town/v/tmcw/privateVal
Hello world!
```
### Browsers
Both Val Town and Deno aim to use the web platform and build
on web standards, so many vals will also run in browsers!
Our module endpoint will transpile TypeScript code to JavaScript
code when it receives a request that meets the criteria:
- The requester is not Deno.
- Neither `text/html` nor `text/tsx` are specified in the HTTP
Accept header.
This means that if you go to a Val's source code directly on
its esm.town URL, you'll see TypeScript, but if you import
that code via JavaScript in the browser, your browser will
get the code as JavaScript.
```html
```
To use a val from a browser, you'll need to import it from
script with `type="module"` - Vals use the [ES Module Syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules).
**Caveats**: While we support Web Platform APIs, we also support
many APIs that aren't available in web browsers. Unlike web browsers,
Deno can read files, interact with environment variables, and much
more. Vals that use these Deno or Node-specific APIs will not
automatically work in browsers.
### Node.js
Using [Node.js](https://nodejs.org/en) with Vals directly relies on relatively new APIs in Node.js,
specifically the [support for HTTP and HTTPS imports](https://nodejs.org/api/esm.html#https-and-http-imports). This requires you to run
Node with a the `--experimental-network-imports` flag, and
has a number of caveats. However, for simple vals, it works.
Create a JavaScript file that Node.js will execute as ESM,
in this case prompting that by using the `.mjs` extension.
```js
// index.mjs
import rand from "https://esm.town/v/tmcw/randomVal";
console.log(rand);
```
Then run Node.js with the specified flag:
```sh
$ node --experimental-network-imports index.mjs
0.4050279110448427
```
### Node.js with dnt
The more robust way of interoperating with Node.js is to use
[dnt](https://github.com/denoland/dnt). dnt is a tool that
can repackage code written for Deno as NPM modules that are
fully compatible with Node.js. It requires a bit more setup than
the other options, but if you're using Deno and NPM APIs and
have more complex code, it can be a great option.
You'll need to install and use Deno locally to run dnt.
Create a shim file that exports from your Val (dnt doesn't support
entry points over HTTP):
```ts
// index.ts
export * from "https://esm.town/v/tmcw/randomVal?v=3";
```
Create a build file like:
```ts
// build.ts
import { build, emptyDir } from "https://deno.land/x/dnt/mod.ts";
await emptyDir("./npm");
await build({
entryPoints: ["index.ts"],
outDir: "./npm",
typecheck: false,
shims: {
deno: true,
},
package: {
name: "your-val",
version: Deno.args[0],
description: "Your package.",
},
postBuild() {},
});
```
Run that build file with Deno:
```sh
$ deno run -A build.ts
[dnt] Transforming...
[dnt] Running npm install...
added 3 packages, and audited 4 packages in 543ms
found 0 vulnerabilities
[dnt] Building project...
[dnt] Type checking ESM...
[dnt] Emitting ESM package...
[dnt] Emitting script package...
[dnt] Running post build action...
[dnt] Running tests...
> test
> node test_runner.js
[dnt] Complete!
```
And now you'll get a directory named `npm` that contains a Node-compatible
module:
```sh
$ npm node
Welcome to Node.js v18.18.0.
Type ".help" for more information.
> require('./')
0.402717678920262
{}
```
### Bun
[Bun](https://bun.sh/) has Node.js compatibility but doesn't support
HTTP imports yet, so the best way to use Vals with Bun is to run them
through dnt using the information above.
---
# Basic examples
URL: https://docs.val.town/vals/http/basic-examples.md
import Val from "@components/Val.astro";
HTTP vals, unlike other vals, expose a public endpoint.
An HTTP val that works with the Web API takes a Request object as input and returns a Response object.
The most basic example can ignore the request and just return a response, even using a shorthand method like [Response.json, which is part of the web standard](https://developer.mozilla.org/en-US/docs/Web/API/Response/json_static).
A slightly more built-out val can return HTML along with the correct Content-Type header:
Now let's work with the request: this echoes request headers back in the response:
We can grab query parameters using the web standard [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams) class:
You can also get the body of POST requests using the Request object,
for example if you had an endpoint that received JSON:
To obtain the HTTP endpoint for your HTTP val, use the "Copy HTTP endpoint" feature (located in the val's sidebar, or in its ... menu).
---
# CDNs and Cache Control
URL: https://docs.val.town/vals/http/cdns.md
When your HTTP val becomes popular or needs to serve visitors around the world quickly, you may want to use a Content Delivery Network (CDN) to cache and serve your content.
**CDNs** are a network of servers that cache your content in multiple locations around the world, allowing users to access it from the nearest server, reducing latency and improving load times. Popular CDNs include [Cloudflare](https://www.cloudflare.com/), [Fastly](https://www.fastly.com/), [AWS CloudFront](https://aws.amazon.com/cloudfront/), and [Vercel](https://vercel.com/).
For example, [the Val Town Blog](https://blog.val.town) is [hosted on Val Town](https://www.val.town/x/valdottown/blog), but served through Cloudflare's CDN.
### Cloudflare CDN
1. Set up your [Val Town Custom Domain](/vals/http/custom-domains) via Cloudflare's DNS management
2. Ensure that proxying (the orange cloud) is enabled.
3. Ensure the CDN configuration for that domain enables caching
4. Add a Cache Rule to enable caching for all content on that endpoint, respecting the cache control headers set by your val. This is important because the default setting is to not cache anything.
5. Add `Cache-Control` headers in your val ([learn more](#cache-control-headers))
6. Debug your CDN configuration by checking the response headers your browser's devtools or via curl. If you see `cf-cache-status: HIT`, then your CDN is working correctly. If you see `cf-cache-status: DYNAMIC`, then the CDN doesn't think the content is cacheable and you may need to adjust your Cloudflare Cache Rule.
If you have any issues, we're happy to help via email: help@val.town.
### Fastly CDN
We confirmed that Fastly works with Val Town custom domains, but we don't have a step-by-step guide yet. If you want to help us write one, please reach out!
### Cache Control Headers
Cache control headers are HTTP headers that specify how and for how long a resource should be cached by browsers and CDNs.
#### Basic cache control headers
```ts
export default (req: Request) => {
return new Response("Hello, world!", {
headers: {
"Cache-Control": "public, max-age=3600", // Cache for 1 hour
},
});
};
```
#### Hono
```ts
import { cache } from "hono/cache";
import { Hono } from "hono";
const app = new Hono();
app.get(
"*",
cache({
cacheName: "my-app",
cacheControl: "public, max-age=300", // cache for 5 minutes
wait: true,
})
);
app.get("/", (c) => {
return c.html("Hello, world!");
});
```
### Disabling caching during development
It can be very fustrating if you forget that caching is enabled while you're actively developing your val. You may not see your changes immediately, or you may see stale content.
The simplest way is to disable caching in your browser. For example, in Chrome, you can open DevTools (F12), go to the Network tab, and check "Disable cache" while DevTools is open.
We also recomend disabling all caching for `*.val.run` domains, so that you can test your changes without ever worrying about caching. This way caching will only be enabled for custom domains.
For example:
```ts
export default (req: Request) => {
const url = new URL(req.url);
return new Response("Hello, world!", {
headers: {
"Cache-Control": url.hostname.endsWith(".val.run")
? ""
: "public, max-age=3600",
},
});
};
```
Or in Hono:
```ts
app.get("*", async (c, next) => {
const url = new URL(c.req.url);
if (url.hostname.endsWith(".val.run")) {
return next();
}
return cache({
cacheName: "my-app",
cacheControl: "public, max-age=300",
wait: true,
})(c, next);
});
```
---
# Custom domains
URL: https://docs.val.town/vals/http/custom-domains.md
:::note
Custom domains are available only to [Pro subscribers](https://www.val.town/pricing).
:::
Custom domains allow you to use your own domain names to access the HTTP vals you created. This feature enables a branded and professional experience for users visiting your vals.
## Buy a domain
The custom domains feature works with domains that you already own, and connects them to your val, which serves all traffic to the domain. You'll need to configure DNS settings with your domain registrar to make this work.
Some domain registrars that you can use to buy a domain:
- [Namecheap](https://www.namecheap.com/)
- [Cloudflare Registrar](https://www.cloudflare.com/products/registrar/)
- [GoDaddy](https://www.godaddy.com/)
## Add your domain to Val Town
Once you have a domain, navigate to Val Town's [custom domain settings](https://val.town/settings/domains) and add the domain that you want to redirect to one of your vals.
Then choose a val to handle requests to that domain.
## Configure the DNS
Lastly, you'll need to configure the DNS settings in your domain registrar so that requests are routed to your val. We provide a summary of the information below on the domain page for your convenience.
The DNS configuration will depend on the type of domain, apex domains (`example.com`) or subdomains (`something.example.com`). These settings will depend on the platform you use to manage your domains.
:::note
DNS changes can take up to an hour to take effect.
:::
### Subdomains
Create a CNAME record to `domains.val.run.`:
| CNAME record | value |
| ------------------------ | ------------------ |
| `something.example.com.` | `domains.val.run.` |
Replace `something.example.com.` with your domain. In some domain platforms, you should only use the subdomain, which would be `something` in the example above.
### Apex domains
Create the following two A records:
| A record | value |
| -------------- | --------------- |
| `example.com.` | `75.2.96.173` |
| `example.com.` | `99.83.186.151` |
Replace `example.com.` with your domain. In some domain platforms, you should replace `example.com.` with `@`.
:::note
Some registrars, such as [Cloudflare](https://developers.cloudflare.com/dns/cname-flattening/), support CNAME records for apex domains as well.
:::
## Debugging
:::tip
Use this [DNS Debugger](https://stevekrouse-dns_record_debugger.web.val.run/) to see the uncached settings of your domain
:::
Unfortunately, custom domains can take minutes or hours to propagate,
which is quite frustrating because you don't know if you just need to wait
or if your settings are incorrect.
Even worse, the domain settings can be cached in multiple places,
including some outside of your control, including your local network.
So if you check your custom domain too early, then you'll continue to not see
it working, even when it's properly working on systems that don't have it cached.
(If we had a dollar every time a user sent us a message asking for help
getting their custom domain working, and when we checked it was already working,
we'd have almost ten dollars.)
In other words, use our [DNS Debugger](https://stevekrouse-dns_record_debugger.web.val.run/)
to see if your custom domain is correctly set up.
---
Val Town custom domains are powered by [SaaS Custom Domains](https://saascustomdomains.com).
---
# Custom subdomains
URL: https://docs.val.town/vals/http/custom-subdomains.md
[HTTP endpoints](/vals/http/) within [Projects](/projects/) can claim custom subdomains
under `val.run`. To choose a custom subdomain, click the edit button in the
preview pane and choose a name.

### Subdomain naming
Subdomain names need to be valid for the DNS system, so they can contain
letters, dashes, and numbers, but they cannot contain two consecutive dashes
`--`, or start or end with a dash. They can be anywhere from 3-63 characters
long.
### Claiming and changing subdomain names
Subdomains are first-come first-served: once you claim a subdomain,
it is attached to your account and can be used for your HTTP endpoints.
For security reasons and to prevent subdomain takeovers,
if you claim a subdomain name and rename an HTTP endpoint
_away_ from that name, the old name will not be accessible to anyone else
for a certain period of time.
Due to the exclusivity of subdomains, there are limits on how many domains
you can use in your account and how many times you can change the subdomain
of an HTTP endpoint.
### Custom domains
You can go even farther with [custom _domains_](/vals/http/custom-domains/),
which allow HTTP endpoints to be hosted behind any top-level domain that
you purchase, or subdomain of a domain that you own.
---
# Early Return
URL: https://docs.val.town/vals/http/early-return.md
import Val from "@components/Val.astro";
Sometimes you need to respond quickly to an incoming HTTP request, and then do other work _after_ sending the response. This makes for snappier UIs for users, and some webhook platforms require responses within a couple seconds. We recommend accomplishing this today by using a queue.
### How to set up a queue
In your early-returning HTTP val:
```ts title="early-returning.ts" val
async function handle(request: Request) {
// Send off the relevant data a queue HTTP val.
// This `fetch` is not awaited.
fetch("https://my-queue.web.val.run", {
method: "POST",
body: req.body,
});
// Respond immediately, before the queued work is done
return Response.json("ok");
}
```
Your queue is another HTTP val that listens for requests and processes them in their own time. Nobody is waiting for the response from the queue, so it can take as long as it needs to:
```ts title="queue.ts" val
async function handle(request: Request) {
// Do some work with the request body that takes a long time
await doSomeWork(request.body);
return Response.json("ok");
}
```
### waitUntil
This technique is a stopgap until full support for
[waitUntil](https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil) lands in Val Town, which will handle this case more explicitly.
### Promises should otherwise be awaited
While this is a useful technique, it's important to emphasize that,
in general, you should otherwise `await` every Promise.
If you don't remember to use `await` with Promises - besides this
narrow use-case, errors that occur in promises won't be properly
handled and functions may run out-of-order.
For example, if you use `fetch` to request some resource,
but forget to await it, then it won't throw errors when it fails,
and you won't have the values you expect to have:
```ts
export async function handle() {
try {
// Result will be an opaque Promise, not a useful value.
const result = fetch("https://google.com/");
} catch (e) {
// Errors will never be caught here because
// fetch is not awaited.
handleError(e);
}
}
```
### HTTP Val Lifecycle
When your HTTP val receives a request, we spin up a process to serve it.
We keep this process alive for some amount of time after the response is sent
to be ready for any other requests that might come its way. After some time of
inactivity, your HTTP process is terminated. This means that if you fire off Promises without awaiting them, they may or may not finish before the val times out, depending on how long they take and whether the val is being kept alive to serve other traffic. The safest path is to await all `Promise`s or make yourself a queue.
---
# HTTP
URL: https://docs.val.town/vals/http.md
import Val from "@components/Val.astro";
import { LinkCard } from "@astrojs/starlight/components";
HTTP vals let you expose an API or website from your val.
They are built on the web-standard
[Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and
[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects,
so are compatible with a number of web frameworks like
[Hono](https://hono.dev/) and [Peko](https://github.com/sejori/peko).
These handlers need to export a function that takes a `Request` object
as the first parameter and returns a `Response` object. The function
can be asynchronous.
---
# HTML & JSX
URL: https://docs.val.town/vals/http/jsx.md
import Val from "@components/Val.astro";
Val Town supports server-rendered JSX. This lets you use JSX syntax to construct
your HTML string. It does not include any client-side JSX framework features,
such as re-rendering on the client, but you can string that together yourself
by writing client-side vals.
To use JSX, you'll need to insert what TypeScript calls a
"[per-file pragma](https://www.typescriptlang.org/tsconfig#jsxImportSource)" - a
comment that uses `@jsxImportSource` to specify where the JSX methods are going
to come from. For example, if you're implementing JSX with Preact, the pragma
will look like this at the top of your val:
```tsx
/** @jsxImportSource https://esm.sh/preact */
```
### Preact
A good default is [Preact](https://github.com/preactjs/preact), which provides a
nice `preact-render-to-string` module that lets you quickly turn that JSX object
into a string that you can use for a response.
### React
### Vue
### Solid
### Hono
:::tip[Interoperability]
Deno has JSX and TypeScript support, but browsers do not. But you
can still use your vals in browsers and systems that don't support
these technologies when you use the [esm.town module endpoint](/api/esm-town) because we automatically transpile their code.
:::
---
# Routing
URL: https://docs.val.town/vals/http/routing.md
import Val from "@components/Val.astro";
One of the coolest things about the Request/Response API is that it works with modern web frameworks, so you can use routing and their helper methods! When an
HTTP val is deployed, it is available at a subdomain like
`handle-valname.web.val.run`, and requests to any subdirectory
or path will be routed to the val.
### Hono
Here's an example with [Hono](https://hono.dev/):
### Peko
And one with [Peko](https://peko.deno.dev/):
### nhttp
And [nhttp](https://github.com/nhttp/nhttp):
### itty-router
A super tiny example with [itty-router](https://itty.dev/itty-router):
### feTS
A simple example of using [feTS server](https://the-guild.dev/openapi/fets/server/quick-start):
Notice, that it exports the `router`, which allows to use the [feTS client](https://the-guild.dev/openapi/fets/client/quick-start) to make routes type safe:
```tsx val
import { type router } from "https://esm.town/v/user/fetsServer";
import { createClient } from "npm:fets";
const client = createClient({
endpoint: "https://user-fetsServer.web.val.run",
});
// The `response` and `greetings` have proper types automatically inferred
const response = await client["/greetings"].get();
const greetings = await response.json();
console.log(greetings);
```
---