# 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 write and scale serverless JavaScript.
Create APIs, crons, and store data β in your browser, deployed in milliseconds.
## Where to start
If you're new to Val Town and want to learn the core building blocks first, read: [What is a val?](/vals/)
If you'd prefer to jump right into the code, we recommend:
## Learn more
##### Local development
Edit your vals from your favorite IDE (VSCode, Cursor, vim, etc) via our CLI or MCP server.
##### Val Town Standard Library
Our standard library includes some hosted services for data storage, email, and AI.
##### API and 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).
---
# 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
---
# Store data in Airtable
URL: https://docs.val.town/guides/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).
---
# 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)
---
# 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 file 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://docs-embed.val.run/](https://docs-embed.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 remix. 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
_Note: anyone can see the embed and the code, but only logged in users will be able to run it._
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)
---
# 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
We have examples for receiving webhooks from [Github](/guides/github/receiving-a-github-webhook), [Telegram](/guides/telegram/#2-create-a-webhook-handler-to-receive-messages), [Stripe](/guides/stripe/#example-val-town-stripe-webhook), and [Supabase](/guides/supabase). Let us know if there are other integrations you'd like us to document!
---
# Your first website
URL: https://docs.val.town/guides/first-website.md
import Val from "@components/Val.astro";
This is a guide to making a website on Val Town using React JSX, such as a link-in-bio page or a personal website.
#### Step 1: Sign up to Val Town
[Sign up to Val Town](https://www.val.town/auth/signup). It's free.
#### Step 2: Remix this template
The quickest way to get started in Val Town is to remix someone else's val. Remixing a val creates a copy of the val in your own account, which you can then edit.
1. Go to https://www.val.town/x/valdottown/linkInBioTemplate
2. Click on the **Remix** button in the top right corner.
##### Source Code
##### Output
#### Step 3: Customize it!
1. Edit your name in `
` in `h1`
2. Edit your self-description line
3. Customize your links
4. Customize the styles
5. Highlight a block of text and ask AI to rewrite it to suit your needs
6. Ask [Townie](https://www.val.town/townie), our AI coding assistant, to help you with any of the above
#### Next steps
π₯³ Congratulations! You now have a live website with a URL that you can share.
You can add a [custom subdomain](/vals/http/custom-subdomains/) (ie `steve.val.run`) or [custom domain](/vals/http/custom-domains/). The following website were made with Val Town:
- [jxnblk.com](https://jxnblk.com/)
- [willkrouse.com](https://willkrouse.com)
- [stevekrouse.com](https://stevekrouse.com)
- [alex-we.in](https://alex-we.in/)
- [blog.val.town](https://blog.val.town)
- ...add yours here! (via the edit button below)
---
# 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.

---
# Send & receive Gmail
URL: https://docs.val.town/guides/gmail.md
## Gmail forwarding to Val Town
You can set up email forwarding from your Gmail account to your Val Town email address.
1. Create a val with an [email trigger](https://docs.val.town/vals/email/)
2. In that val, log the email object to the console:
```ts
export default async function (e: Email) {
console.log(e);
}
```
3. Copy that email trigger's email address
4. In [Gmail Forwarding and POP settings](https://mail.google.com/mail/u/0/#settings/fwdandpop), click `Add forwarding address`. If you do not see that button, click `creating a filter`, then add some arbitrary search criteria and click `Create filter`, then you should see the `Add forwarding address` button, which should take you to another `Add forwarding address` button.Paste your val's email address and click `Next` β `Proceed` β `OK`.
5. Gmail will send a confirmation to your val. View the val's logs and click on the confirmation link:

That URL will likely end in `/r/n`, which will make it invalid. Remove the `/r/n` to get it to work. Then click `Confirm`.
6. Back in Gmail settings, you can now set up a filter to forward certain emails to your val's email address. Or you can forward all emails by selecting `Forward a copy of incoming mail to `.
7. Click `Save Changes` at the bottom of the page.
## Sending email from Val Town using Gmail SMTP
You can send email from your val using Gmail's SMTP server.
1. In your Google account, go to [Security settings](https://myaccount.google.com/security) and enable 2-Step Verification.
2. After enabling 2-Step Verification, go to [App Passwords](https://myaccount.google.com/apppasswords) and create an app password for "Mail" on "Other" (name it "Val Town" or something similar). Copy the generated password.
3. In your val, set the following environment variables:
- `GMAIL_USER`: Your full Gmail email address
- `GMAIL_PASS`: The app password you generated in step 2
4. Use the following code to send an email using Gmail's SMTP server:
```ts
import nodemailer from "npm:nodemailer";
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: Deno.env.get("GMAIL_USER"),
pass: Deno.env.get("GMAIL_PASS"),
},
});
const info = await transporter.sendMail({
from: Deno.env.get("GMAIL_USER"),
to: "steve@val.town",
subject: "Test email from Val Town",
text: "This is a test email sent from Val Town using Gmail SMTP.",
});
console.log(info);
```
5. Run your val and check the logs and recipient's inbox.
You can ignore any `Failed to resolve IPv4 addresses with current network` message. Nodemailer first tries IPv4 then IPv6, so if IPv4 fails it falls back to IPv6.
### Getting email from Gmail IMAP
You can also fetch emails from your Gmail account using the IMAP protocol.
1. In your Google account, go to [Security settings](https://myaccount.google.com/security) and enable 2-Step Verification.
2. After enabling 2-Step Verification, go to [App Passwords](https://myaccount.google.com/apppasswords) and create an app password for "Mail" on "Other" (name it "Val Town" or something similar). Copy the generated password.
3. Set up the following environment variables in your val:
- `GMAIL_USER`: Your full Gmail email address
- `GMAIL_PASS`: The app password you generated in the previous section
4. Use the following code to fetch emails using IMAP:
```ts
import { ImapFlow } from "npm:imapflow";
const client = new ImapFlow({
host: "imap.gmail.com",
port: 993,
secure: true,
auth: {
user: Deno.env.get("GMAIL_USER"),
pass: Deno.env.get("GMAIL_PASS"),
},
});
await client.connect();
await client.mailboxOpen("INBOX");
for await (let message of client.fetch("1:*", { envelope: true })) {
console.log(message.envelope);
}
await client.logout();
```
---
# Your first scheduled cron
URL: https://docs.val.town/guides/first-cron.md
This is a guide to creating a cron job that sends you a weather email every morning.
### 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: Set up a Cron Trigger
Create a new val. In a new file, set the trigger type to **Cron**.
This will create a scheduled trigger associated with this file. The default code will look like this:
```ts title="weatherNotifier.ts"
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 on the Cron in the top right corner of the file.
### 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}
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 importing code from another val into this file. This is a common pattern in Val Town. Learn more about [importing here](/reference/import/).
:::
### 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 Job
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 (or find the schedule settings) in the file editor for your `weatherNotifier.ts` file.
3. Click **Cron**
4. Paste in your cron expression.
### Next steps
π₯³ Congratulations! You've created a Cron job 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 get 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)
---
# Store data in a Google Sheet
URL: https://docs.val.town/guides/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]);
```

---
# In, Val, Out
URL: https://docs.val.town/guides/in-val-out.md
import { CardGrid, LinkCard, Steps } from "@astrojs/starlight/components";
A common pattern in Val Town is what we call _In, Val, Out_, or Input/Val/Output (I/V/O). You have data coming in, and you want to do something with it then output something. For example:
1. Webhook received from Supabase when a user signs up
2. Enrich the user data with Clay
3. Send your team a Slack notification
There's a combinatorial explosion of possibilities, so we've listed some examples and ideas. Feel free to add new use cases, or [ask](https://discord.gg/dHv45uN5RY) [us](mailto:docs-help@val.town) to lend a handβwe're happy to help.
## Input
**Messages, email:**
**Events:**
You can hook up any tool in your stack as long as it can send data to your val's HTTP endpoint or email. That might be data from Clerk, Vercel, Linear, Sentry, Cal.com, Shopify, PostHog, Cloudflare, Google Forms, Typeform, or anywhere elseβjust send it to a val.
## Val
**AI enrichment, browser automation:**
You can also call your LLM of choice, like Claude, or run arbitrary enrichment and analysis using other tools, like Exa.
## Output
**Notifications:**
Whether you use Resend, Loops, Twilio, or some other platform, as long as it has an API you can use it in Val Town.
**Persistence:**
You can of course store your data in Cloudflare R2, Notion, or any other database or CRM. Let us know if you'd like help with your use case, and we'll continue to add examples to this docs page.
---
# 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.
[Pushover template val](https://www.val.town/x/jacobbudin/pushover/code/main.ts)
---
# 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 using a [Cron trigger](/vals/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
---
# 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.
---
# 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 an [HTTP trigger](/vals/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 trigger](/vals/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.
---
# Store data in AWS S3
URL: https://docs.val.town/guides/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)!
---
# 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](/reference/std/blob) is a very simple key-value store for storing files, JSON, images, large blobs of data, while [SQLite](/reference/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 remix 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](/reference/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 remix 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](/reference/std/sqlite).
---
# SQLite wasm
URL: https://docs.val.town/guides/sqlite-wasm.md
:::caution[Deprecated]
New vals [should use `std/sqlite` instead](/reference/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 webhooks
URL: https://docs.val.town/guides/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 using an HTTP trigger. 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/guides/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/guides/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
Remix this val to set up your webhook handler: [@std/telegramBotStarter](https://www.val.town/x/std/telegramBotStarter)
Go to your val's environment variables. 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).
---
# 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.
---
# Legacy Vals [DEPRECATED]
URL: https://docs.val.town/legacy-vals.md
:::danger[Deprecated]
The concept of single-file "Legacy Vals" and separate "Projects" has been replaced by a unified **Val** primitive β a collaborative folder for deployed code.
All legacy vals have been automatically upgraded. Read our [blog post](https://blog.val.town/upgrading) for migration details.
Please refer to the main [Vals documentation](/vals/) for current information.
:::
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
---
# Supabase webhooks
URL: https://docs.val.town/guides/supabase.md
import Val from "@components/Val.astro";
Handling Supabase webhooks in Val Town allows you to run arbitrary logic whenever an `INSERT`, `UPDATE`, or `DELETE` happens in your database. For example, for new user signups you could set up an [email](/reference/std/email), [Slack](/guides/slack/send-messages-to-slack), or [Discord](/guides/discord/send-message) notification, or [enrich user data with Clay](https://www.val.town/x/charmaine/clay-proxy).
## Create an HTTP trigger in Val Town
Create a new val using the webhook template, or remix this example val:
## Configure webhook(s) in Supabase
Enable [Database > Webhooks](https://supabase.com/dashboard/project/_/guides/webhooks/webhooks) in the Supabase dashboard, which will install as an Integration:

To wire up your HTTP val:
1. Click "Create a new hook"
2. Name your webhook
3. Select what table (e.g. users) and events (e.g. INSERT) to hook into
4. Copy your val's HTTP endpoint and paste it in
5. Create a [random secret](https://randomkeygen.com) and add it as a header

6. Store the secret as an [environment variable](/reference/environment-variables/) in your val

Reference Supabase's [database webhooks docs](https://supabase.com/docs/guides/database/webhooks) for more information about payloads, monitoring, and local development.
## Verify webhooks
The hard-coded secret we added above is quick but imperfect security. For more robust signature verification, you can reference [this `supabaseVerifiedWebhook` val](https://www.val.town/x/petermillspaugh/supabaseVerifiedWebhook).
---
# Website Uptime Tracker
URL: https://docs.val.town/guides/website-uptime-tracker.md
import Val from "@components/Val.astro";
Examples from the community:
---
# Express
URL: https://docs.val.town/legacy-vals/express.md
:::caution[Deprecated]
New vals [should use the Web API instead](/vals/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");
};
```
---
# 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/browser-automation/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).
---
# File I/O
URL: https://docs.val.town/reference/file-io.md
import { LinkCard } from "@astrojs/starlight/components";
There is no direct access to the filesystem in Val Town. This allows us to deploy and scale your code quickly and efficiently. If you need to read files from within a val, you can via the [Val Town API](/reference/api).
## Using the SDK
### Read a file
[Docs](https://sdk.val.town/api/node/resources/vals/subresources/files/methods/getContent)
```ts val title="read-file.ts"
import ValTown from "npm:@valtown/sdk";
const vt = new ValTown();
const val_id = "your-val-id-here";
const response = await vt.vals.files.getContent(val_id, {
path: "path/to/file.txt",
});
const content = await response.text();
console.log(content);
```
### Create a file
[Docs](https://sdk.val.town/api/node/resources/vals/subresources/files/methods/create)
```ts title="create-file.ts" val
import ValTown from "npm:@valtown/sdk";
const vt = new ValTown();
const val_id = "your-val-id-here";
const file = await vt.vals.files.create(val_id, {
path: "path",
type: "directory",
});
```
### Update a file
[Docs](https://sdk.val.town/api/node/resources/vals/subresources/files/methods/update)
```ts title="update-file.ts" val
import ValTown from "npm:@valtown/sdk";
const vt = new ValTown();
const val_id = "your-val-id-here";
const file = await vt.vals.files.update(val_id, {
path: "path/to/file.txt",
content: "New file content",
});
```
### List files
[Docs](https://sdk.val.town/api/node/resources/vals/subresources/files/methods/retrieve)
```ts title="list-files.ts" val
import ValTown from "npm:@valtown/sdk";
const vt = new ValTown();
const val_id = "your-val-id-here";
const files = await Array.fromAsync(
vt.vals.files.retrieve(id, {
path: "",
recursive: true,
})
);
```
## Using val utils
Val Town's standard library ships with a suite of utility functions like [`readFile`](#readfile) and [`serveFile`](#servefile).
### `readFile`
Reads the contents of a file from the current val.
**Signature:**
```ts
readFile(path: string, metaImportUrl?: string): Promise
```
**Parameters:**
- `path` - The file path to read (with or without leading slash)
- `metaImportUrl` - Optional. The `import.meta.url` to determine which val to read
from. Defaults to `Deno.env.get("VALTOWN_ENTRYPOINT")`
**Example:**
```ts
import { readFile } from "https://esm.town/v/std/utils/index.ts";
// Most common usage - reads from current val
const content = await readFile("/README.md");
console.log(content);
// Explicit val specification
const content2 = await readFile("/README.md", import.meta.url);
console.log(content2);
```
### `listFiles`
Lists all files in the current val with their metadata.
**Signature:**
```ts
listFiles(metaImportUrl?: string): Promise
```
**Parameters:**
- `metaImportUrl` - Optional. The `import.meta.url` to determine which val to list
files from. Defaults to `Deno.env.get("VALTOWN_ENTRYPOINT")`
**Example:**
```ts
import { listFiles } from "https://esm.town/v/std/utils/index.ts";
// Most common usage - lists files from current val
const files = await listFiles();
files.forEach((file) => console.log(file.path));
// Explicit val specification
const files2 = await listFiles(import.meta.url);
files2.forEach((file) => console.log(file.path));
```
### `listFilesByPath`
Lists files in a specific directory path within the current val.
**Signature:**
```ts
listFilesByPath(path: string, metaImportUrl?: string): Promise
```
**Parameters:**
- `path` - The directory path to list files from
- `metaImportUrl` - Optional. The `import.meta.url` to determine which val to list
files from. Defaults to `Deno.env.get("VALTOWN_ENTRYPOINT")`
**Example:**
```ts
import { listFilesByPath } from "https://esm.town/v/std/utils/index.ts";
// List files in a specific directory
const frontendFiles = await listFilesByPath("frontend/");
frontendFiles.forEach((file) => console.log(file.path));
```
### `httpEndpoint`
Gets the HTTP endpoint URL for a specific file in the val.
**Signature:**
```ts
httpEndpoint(path: string, metaImportUrl?: string): Promise
```
**Parameters:**
- `path` - The file path to get the endpoint for
- `metaImportUrl` - Optional. The `import.meta.url` to determine which val the
file belongs to. Defaults to `Deno.env.get("VALTOWN_ENTRYPOINT")`
**Example:**
```ts
import { httpEndpoint } from "https://esm.town/v/std/utils/index.ts";
// Get the HTTP endpoint for a specific file
const endpoint = await httpEndpoint("/api/users.ts");
console.log(endpoint); // Returns the full HTTP URL for the endpoint
```
### `emailAddress`
Gets the email address for a file with an email trigger in the val.
:::note
This function only works for the uuid version and not vanity email addresses because that info isn't in our API yet. Open a [GitHub feature request](https://github.com/val-town/val-town-product/discussions/categories/feature-request) if you'd like us to prioritize this.
:::
**Signature:**
```ts
emailAddress(path: string, metaImportUrl?: string): Promise
```
**Parameters:**
- `path` - The file path to get the email address for
- `metaImportUrl` - Optional. The `import.meta.url` to determine which val the
file belongs to. Defaults to `Deno.env.get("VALTOWN_ENTRYPOINT")`
**Example:**
```ts
import { emailAddress } from "https://esm.town/v/std/utils/index.ts";
// Get the email address for a file with email trigger
const email = await emailAddress("/handlers/processEmail.ts");
console.log(email); // Returns: username-fileid@valtown.email
```
**Notes:**
- The file must have an email trigger configured
- Returns email in format: `{username}-{fileIdWithoutDashes}@valtown.email`
- Throws an error if the file doesn't exist or doesn't have an email trigger
### `fetchTranspiledJavaScript`
Fetches and transpiles TypeScript/JavaScript code from esm.town URLs.
**Signature:**
```ts
fetchTranspiledJavaScript(url: string): Promise
```
**Example:**
```ts
import { fetchTranspiledJavaScript } from "https://esm.town/v/std/utils/index.ts";
const code = await fetchTranspiledJavaScript(
"https://esm.town/v/std/utils/index.ts"
);
console.log(code);
```
**Notes:**
- Paths can be specified with or without leading slashes
- TypeScript files are automatically transpiled to JavaScript
- Most functions now have optional `metaImportUrl` parameters that default to
`Deno.env.get("VALTOWN_ENTRYPOINT")`, making them easier to use in most cases
### `serveFile`
Serves a file from the val as an HTTP Response with the correct Content-Type
header.
**Signature:**
```ts
serveFile(path: string, metaImportUrl?: string): Promise
```
**Parameters:**
- `path` - The file path to serve
- `metaImportUrl` - Optional. The `import.meta.url` to determine which val to
serve from. Defaults to `Deno.env.get("VALTOWN_ENTRYPOINT")`
**Example:**
```ts
import { serveFile } from "https://esm.town/v/std/utils/index.ts";
// In a Hono app - most common usage
app.get("/assets/*", async (c) => {
return await serveFile(c.req.path);
});
// Explicit val specification
app.get("/assets/*", async (c) => {
return await serveFile(c.req.path, import.meta.url);
});
```
### `getContentType`
Determines the appropriate MIME type for a file based on its extension.
**Signature:**
```ts
getContentType(path: string): string
```
**Example:**
```ts
import { getContentType } from "https://esm.town/v/std/utils/index.ts";
console.log(getContentType("index.html")); // "text/html; charset=utf-8"
console.log(getContentType("script.ts")); // "text/javascript"
console.log(getContentType("data.json")); // "application/json; charset=utf-8"
```
**Features:**
- TypeScript/TSX/JSX files are served as `text/javascript`
### `parseVal`
Parses Val Town metadata from an `import.meta.url`.
**Signature:**
```ts
parseVal(importMetaUrl: string): ValMetadata
```
**Example:**
```ts
import { parseVal } from "https://esm.town/v/std/utils/index.ts";
const val = parseVal(import.meta.url);
console.log(val);
// Output: { owner: "username", name: "valname", ... }
```
**Prior Art:**
- [@pomdtr/extractValInfo](https://www.val.town/v/pomdtr/extractValInfo)
- [@easrng/whoami](https://www.val.town/v/easrng/whoami)
### `staticHTTPServer`
Creates a Hono-based HTTP server that serves static files from your val. The
server automatically serves `/index.html` for root requests and handles all
other file paths.
**Signature:**
```ts
staticHTTPServer(importMetaURL?: string): (request: Request) => Promise
```
**Parameters:**
- `importMetaURL` - Optional. The `import.meta.url` to determine which val to
serve files from. Defaults to `Deno.env.get("VALTOWN_ENTRYPOINT")`
**Example:**
```ts
import { staticHTTPServer } from "https://esm.town/v/std/utils/index.ts";
// Most common usage - serves files from current val
export default staticHTTPServer();
// Explicit val specification
export default staticHTTPServer(import.meta.url);
```
**Features:**
- Automatically serves `/index.html` for root path requests (`/`)
- Handles all file paths with proper content types
- Built on Hono for reliable HTTP handling
- Includes error unwrapping for better debugging
- Returns a fetch-compatible handler function
**Use Cases:**
- Single-page applications (SPAs)
- Static websites
- Documentation sites
- Asset serving for web applications
**Notes:**
- The server will attempt to serve `/index.html` for root requests
- All other paths are served as-is from the val's file system
- Non-existent files will throw "Failed to fetch file" errors
- The returned function is compatible with Val Town's HTTP trigger system
### `isMain`
Checks if the current file is the main entry point of the val. This is a
replacement for `import.meta.main` which is not available in Val Town's
environment.
**Signature:**
```ts
isMain(importMetaURL: string): boolean
```
**Parameters:**
- `importMetaURL` - The `import.meta.url` of the current file
**Example:**
```ts
import { isMain } from "https://esm.town/v/std/utils/index.ts";
if (isMain(import.meta.url)) {
console.log("This file is being run directly");
// Run main logic here
} else {
console.log("This file is being imported");
// Export functions/classes for use by other files
}
```
**Use Cases:**
- Conditional execution of code only when file is run directly
- Separating library code from executable code in the same file
- Testing and development workflows
- Creating dual-purpose files that can be both imported and executed
**Notes:**
- This function compares the provided `import.meta.url` with the
`VALTOWN_ENTRYPOINT` environment variable
- In Val Town, the entry point is determined by which file is being executed
directly (e.g., via HTTP trigger, cron, or manual execution)
- This is equivalent to Python's `if __name__ == "__main__":` pattern or
Node.js's `require.main === module`
- Unlike Deno's `import.meta.main`, this works reliably in Val Town's serverless
environment
---
# Deno LSP
URL: https://docs.val.town/reference/deno-lsp.md

To provide the best possible editor experience, Val Town runs the real [deno language server](https://docs.deno.com/runtime/reference/lsp_integration/) (LSP) in our code editor.
A "language server" is a program that most languages, including TypeScript, provide to enrich your editing experience, providing things like code diagnostics (such as red squiggles for broken code), hover overs, and "go to definition" capabilities.
At Val Town, we run `Deno`, which is a modern TypeScript runtime with unique features like URL import support and the `Deno` global. You may have noticed this when querying environment variables with `Deno.env.get`. `Deno` is unique enough from regular TypeScript that it provides its own [language server](https://docs.deno.com/runtime/reference/lsp_integration/).
When you load our editor, you will see a small colored dot in the bottom right. This is the language server status indicator. We run language servers on containers in the cloud, and each user gets their own LSP container. This dot indicates your connection state to your LSP container. You can click on it for a dropdown with more information.
We attempt to pre-warm a server for you when you load Val Town so that the LSP is quickly ready when you get to our editor, but sometimes the LSP can take a while to boot (in rare cases, up to 15-20 seconds), typically after using it for the first time in a while. This is normal, and after it loads it will stay warm until a little while after you close the editor.
This dot has 5 ready states:
- **Disconnected**: The LSP is disconnected. If you are ever in this state, try restarting the LSP. Usually we will attempt to automatically do the restart for you if you ever each this state.
- **Connecting**: You are attempting to establish a websocket connect to the LSP. The container may currently still be booting, so you could be stuck in this state for a while if you are using the LSP for the first time in a while.
- **Restarting**: You triggered a restart, and the LSP is in the process of dying.
- **Waiting**: The LSP is connected, and is currently loading your Val and downloading its dependencies. The LSP will be functional in this state but its functionality will be degraded -- you may see "unresolved import" errors, for example, as it attempts to download them.
- **Healthy**: The LSP has done a "handshake" with the editor and cached all of your Val's dependencies. It's ready to go!
We reload the LSP when you load a different Val, or a different branch of the same Val. Currently we only let you run one instance of the LSP at a time. This means that if you are editing some val A, and you open a new tab and begin to edit some val B, tab A will change to "Disconnected" state. When you focus (click) in the editor in tab A again, we will automatically disconnect tab B and reconnect for tab A.
Right now, the language server is only active for Vals that you own when you are interacting with a _non_-read-only editor. This may change in the future.
Right now the LSP is a **beta experience**, and it is possible you may experience bugs. Please reach out if something isn't working as expected, or if you experience random crashes!
#### Features
The LSP experience you will find in our editor is very similar to that of using the Deno LSP locally, but we have added some platform specific additional functionality, like adding environment variable suggestions when you type "Deno.env...", or cautioning you about using jsx in a .ts file.
Language servers have a large amount of features. We support the following subset of the LSP specification:
- Diagnostics (red, yellow, and gray squiggles)
- Completions (the list of suggestions that pop up as you type)
- Code actions (buttons that show up when you hover on yellow squiggles)
- Hover overs
- Go to definition (when you command or control click on a symbol, or press F12 while focused on a symbol)
- Signature reference (a box indicating the arguments a function takes that shows up when you are typing a function invocation)
`Deno` supports a top level [`deno.json`](https://docs.deno.com/runtime/fundamentals/configuration/) file to configure Deno. This is like an all in one `package.json`, `tsconfig.json` and `.eslintrc.js`. If you have a `deno.json` present at the top level of your Val, we will attempt to give the LSP access to it. You may have to restart the LSP after changing it. We suggest you restrict editing of the `deno.json` to the `lint` and `compilerOptions` fields to configure how the LSP works, since we do not currently support running your code with a custom `deno.json`.
#### Formatting
In your editor settings, you can opt in to automatic formatting on save. When you save a file in your Val, it will have the [Deno formatter](https://docs.deno.com/runtime/reference/cli/fmt/) invoked.
You can customize format settings by visiting your editor settings and changing your "Deno fmt" options. This is equivalent to the `"fmt":` field in the `deno.json`. These settings are scoped across your entire account. Currently we do not support the `fmt` field in a `deno.json` for a Val.
For all the format options, [visit this page of `Deno`'s docs](https://docs.deno.com/runtime/fundamentals/linting_and_formatting/).
#### Multi Tab Editing
When editing the same Val, we support multi-tab editing. Just like your local editor would, we share the same LSP process across all files for the same Val, and if you have multiple tabs open, they all talk to the same instance of the deno LSP. If you, for example, make a change to the return type of a function in one tab, and you have a file open in another tab that imports and uses that function, that tab may see red squiggles pop up.
One caveat of multi tab editing is that if you attempt to edit the same file in multiple tabs, you will see the same diagnostics across all tabs, even though the current content state of the same file across tabs may differ. We suggest you do not edit the same file in multiple tabs.
#### Restarting the Language Server
Language servers are complicated and delicate. It is possible that the internal state of the language server falls out of sync, and it thinks that your files are slightly different than they actually are. In a situation like this, you may see confusing red squiggles all over the place, code actions that make no sense, or other unexpected behavior.
If you run into a situation where you think the LSP is in a "bad state," you can restart it by clicking the tiny dot in the bottom right, and pressing "Restart." The restart button will kill and respawn the Deno LSP so that it has a clean slate.
We also provide a "Hard Restart" button. This totally shuts down and reboots the entire container (server) that is running the LSP process. You should generally avoid using this unless the LSP is not responding and the Restart button is ineffective. A Hard restart can take up to 15-20 seconds to complete. A regular restart should be very quick.
#### How does this all work?
The Deno language server is part of `Deno`. If you have `deno` installed on your computer, you can actually run `deno lsp` to start the LSP. You won't see anything though, since you have to send special messages to and from the process to "activate" it, which is something your editor (such as vscode, for example) usually does.
To expose the Deno language server to the Val Town editor, we run instances of the Deno LSP on [cloudflare containers](https://developers.cloudflare.com/containers/). The [LSP protocol](https://microsoft.github.io/language-server-protocol/) is relatively straightforward and consists of JSON payloads sent to and from the LSP, like
`{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///path/to/file.ts","languageId":"typescript","version":1,"text":"console.log('hello world!')"}}}`
We serialize and send messages to and from this container over a WebSocket connection to talk to the language server. If you're curious, you can open your browser's network tab and press the LSP "Restart" button, and you will see a Websocket connection be established to `lsp.val.town`, and you can watch the actual LSP messages go back and forth. These messages received from the LSP are transformed in to UI components in our editor, and various editing actions will cause messages to get sent to the LSP.
---
# Environment variables
URL: https://docs.val.town/reference/environment-variables.md
import Val from "@components/Val.astro";
You can store secrets, keys, and API tokens as **Environment Variables** via the val's left side bar.
Environment variables can be accessed via `Deno.env` or `process.env` within any file in your val.
- The "key" and "value" of each environment variable can be any string
- Vals can't set environment variables programmatically: Environment variables are only 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
It is safe to reference environment variables in public vals.
Others can see that they're being used, but not their values.
For example, in this public 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 trigger, the underlying code can be _triggered_ by anyone who knows the URL or email address, but they still can't access your environment variables directly. This is how server-side code normally works. If someone wants to run your code with _their_ environment variables, they need to _remix_ your val and run their copy.
---
# 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](/vals/http/jsx/). [Learn more about when we transpile here.](https://www.val.town/x/stevekrouse/esmTownTranspileDemo)
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](/reference/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
Within vals, you can import files and folders relative to the val root (e.g., `./home`), which means you don't have to update your imports when you branch, remix, or rename a val.
Val source code hosted on [esm.town](/reference/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 an HTTP endpoint within a val, using the concepts described in the [HTTP Triggers documentation](/vals/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.
---
# Using images in vals
URL: https://docs.val.town/reference/images.md
At some point you might want to add an image to a valβin your README, HTML/JSX, or elsewhere. There are four ways to host images in Val Town:
1. Drag and drop images into our markdown editor to auto-upload
1. Store images in our [blob storage](/reference/std/blob)
1. Host images elsewhere and link from vals
1. Store SVGs in vals as files
:::note
We don't currently support binary files in vals, but we plan to at some point. We recommend [blob storage](/reference/std/blob) as a workaround for now. Upvote and comment on [this GitHub discussion](https://github.com/val-town/val-town-product/discussions/21) if that's something you'd like to see soon!
:::
## Drag and drop
While editing markdown in a val, you can drag and drop image files in the editor.
You'll see a toast message while the image is uploading and when it completes. It'll automatically insert the image with its storage URL, like so:
```md

```
Once your image has been uploaded, you can use its storage URL to link to it elsewhere (we just ask that you limit auto-uploads to reasonable use within Val Town and don't abuse it as a storage service).
## Blob storage
Vals come with built-in [blob storage](/reference/std/blob) through our standard library. Keeping images in blob storage can be handy because you'll have a centralized view of all your images (and JSON, text, binary, etc.) in Val Town settings.
Check out this [blob_admin](https://www.val.town/x/stevekrouse/blob_admin) val for an example of both using and managing your images.
## Host elsewhere
If you already use a service like Amazon S3 or Cloudflare R2 for image hosting, no need to change course. Copy-paste your image's source URL in your val's markdown, HTML, JSX, or wherever you're using images.
## SVGs
SVGs can be stored as text files in your vals. You can import the [`serveFile`](/reference/file-io/#servefile) util from our standard library to serve your SVG file (like [this val](https://www.val.town/x/petermillspaugh/dotcom) does, for example).
You can of course include SVGs directly in your markup as HTML or JSX, too.
---
# 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.6
- V8 13.7.152.6-rusty
- TypeScript 5.8.3
---
# 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
- `cmd-d` to select the next instance of the highlighted text with multiple cursors
---
# 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 get the latest version _at the time_, which our lockfile will freeze so that your code runs consistently even if newer versions of the imported file might include breaking changes. 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.
---
# Upgrading Legacy Vals
URL: https://docs.val.town/upgrading/legacy-vals.md
We are unifying _vals_ and _projects_ into a single primitive: the **val** β a collaborative folder of deployed code β that includes all the best features of both.
## Legacy Vals β Projects β Vals & Triggers
The confusing part of this upgrade is that weβre shuffling around some names.
1. _Legacy vals_ will be upgraded to _projects_.
2. After the migration is done, weβll rename _projects_ to _vals_.
3. We'll rename `HTTP`, `Cron`, and `Email` _val types_ to _triggers_.
Like Val Town Projects, these upgraded vals will be able to contain multiple `HTTP`, `Cron`, and `Email` triggers. The `Script` val type is going away; all JavaScript files will be runnable.
We know this name shuffling is confusing. Thanks for bearing with us!
## How upgrading works
When you upgrade your legacy val, weβll migrate it to a Val Town Project. This process is fully automated and takes just a few seconds.
### Naming
- Your val becomes a Val Town Project with the same name.
- If you already have a project with that name, weβll add `_migrated` to the name.
### Code Migration
- Your code moves into a `main.tsx` file.
- Your README becomes the project's `README.md`.
- Version history migrates intact, preserving timestamps.
### Triggers
- All existing HTTP endpoints, crons, email handlers, and custom domains will be preserved.
### Imports
- Imports via `esm.town` will still point to previous versions unless updated manually.
- Relative imports are converted automatically to absolute imports.
### `import.meta.url`
- Common usage of `import.meta.url`, such as `@pomdtr/extractValInfo`, will be upgraded automatically.
### Environment Variables
- Global environment variables automatically transfer (you can disable this in project settings)
### Legacy Val Folders
- Our migration tool doesn't support legacy val folders due to the technical complexity.
- The legacy folder structure will be preserved as a notes at the bottom of your project's `README.md`
Hereβs how you can handle folders:
- **Do nothing:** Upgrade vals individually; they remain separate but functional.
- **Manual consolidation:** Upgrade the main val first, then manually copy & paste other vals' code into the new Val Town Project. This typically takes just a few minutes.
- **Bulk automation:** Quickly migrate source files only using our automated tool at [valtoproject.val.run](https://valtoproject.val.run/). This tool only migrates source files; it doesn't do the other upgrade steps listed above.
**Need help?** Pair with our team by [booking time here](https://calendar.app.google/RV7chTwXAbq4DyYcA).
## How to upgrade your legacy vals
1. Go to your legacy valβs **Settings**.
2. Click **Upgrade**.

## **Next steps**
1. Upgrade early and get priority, hands-on support.
2. Questions? Contact us over [email](mailto:help@val.town) or [Discord](http://discord.val.town/).
3. All remaining legacy vals will be migrated on **April 30, 2025**
4. The migration will be complete on **May 1, 2025**
We appreciate your cooperation and patience during this migration!
---
# 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.
---
# 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!
---
# 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.
---
# Blocked bots
URL: https://docs.val.town/troubleshooting/bot-rate-limiting.md
We block 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/)).
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)` |
---
# 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.
---
# Email
URL: https://docs.val.town/vals/email.md
import Val from "@components/Val.astro";
Email triggers provide a unique email address for your val. When Val Town
receives an email at that address, it triggers the corresponding file within 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](/reference/std/email)
:::
To add an email trigger, click the `+` button in the top right of your val editor and select `EMAIL`.

## Type Signature
Files triggered by Email receive an argument called `Email` that represents the email that was sent. Here's an example:
```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 Email trigger forwards any email it receives to me. Try it out by sending an email to `stevekrouse.forwarder@valtown.email`.
## Custom email address
Similar to [HTTP endpoints](/vals/http/custom-subdomains/), you can set a custom email address by clicking on the `Email` trigger and selecting `Custom email address`.
You can choose any available email address ending in `@valtown.email`.

## Limitations
:::note[Email size limit]
The total size of inbound emails, including attachments, must be less than 30MB.
:::
Email triggers are powered internally by [Sendgrid Inbound Parse](https://www.twilio.com/docs/sendgrid/ui/account-and-settings/inbound-parse).
---
# Branches
URL: https://docs.val.town/vals/branches.md
Every val has a `main` branch, which is the default branch. You can create branches off the `main` branch for feature development, testing, and sharing.
## Managing branches
You need to be the owner of a val to create a branch. If you are not the owner, you can [remix the val](/vals/remixes) and send a pull request.
You can branch off `main` or any other branch from the default val page.
You can create, delete or rename any branch from the val's navigation sidebar.
## 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 val, including file and folder renames, additions, deletions, and relocations.
### Merging from parent branch
You can pull updates from the parent val 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 val 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. You can only merge a branch into what it was originally forked from.β
- There is no support for squashing or rebasing changes.
- You cannot change the name of the `main` branch.
---
# Cron
URL: https://docs.val.town/vals/cron.md
import Val from "@components/Val.astro";
Cron triggers 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
Crons can be configured with [cron syntax](https://en.wikipedia.org/wiki/Cron), or simple intervals like "once an hour"
To add a cron trigger, click the `+` button in the top right of your val editor and select `CRON`.

Functions can run up to once every 15 minutes, or [once a minute, with the Pro plan](https://www.val.town/pricing).
## Type Signature
Crons 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.
---
# Overview
URL: https://docs.val.town/vals.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
Vals are the building blocks of Val Town. They can be used to create APIs, crons, websites, and email handlers with JavaScript.
A val is a collaborative, versioned folder of deployed code. It includes everything you need to run code in the cloud, including:
- [HTTP](/vals/http), [Cron](/vals/cron), and [Email](/vals/email) triggers
- Environment variables [β](/reference/environment-variables/)
- Import from other NPM, JSR, and more [β](/reference/import/)
- Access controls [β](/reference/permissions/)
- Version control & collaboration: reversions, [branching](/vals/branches), [remixes](/vals/remixes), [pull requests](/vals/pull-requests)
- The Deno runtime [β](/reference/runtime/)
- Logs & traces
#### Triggers
There are **3 types of triggers** that JavaScript files within a val can have:
#### Versioning & Collaboration
Every file change is automatically versioned. Rollbacks are instantaneous. You can create branches and remixes for development and collaborating.
---
# Val Limitations
URL: https://docs.val.town/vals/limitations.md
- Maximum files per val: 1000
- Maximum branches per val: 100
- Binary files are not supported. We recommend [Blob Storage](/reference/std/blob).
- Free tier users are limited to 5 private or unlisted vals. Pro users can have unlimited private vals. All users can have unlimited public vals.
Please contact us if you need more resources or have any questions.
---
# Remixes
URL: https://docs.val.town/vals/remixes.md
Remixes are a way to create a copy of a val 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](/vals/pull-requests).
### Remixing
Remixing creates a copy of a val 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 val, and the version is updated to match the latest version in the parent.
---
# Pull Requests
URL: https://docs.val.town/vals/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 val, and they are the primary way to collaborate on vals with other users.
To make a PR, create a remix of the parent val, then visit the parent val's **Pull requests** page where you will see an "Open pull request" button. If you're the owner of the parent val, you can also create a PR from a branch of that val.
---
# Browserbase
URL: https://docs.val.town/guides/browser-automation/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 Browserbase API key and remix 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. Remix [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 recommend 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/guides/browser-automation/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."
```
---
# Kernel
URL: https://docs.val.town/guides/browser-automation/kernel.md
import Val from "@components/Val.astro";
[Kernel](https://onkernel.com/) is a serverless browser automation platform.
It runs full Chrome instances in the cloud and provides a simple WebSocket/HTTP API.
Because Val Town canβt launch a local browser, Kernel lets you use tools like **Puppeteer** or **Playwright** by connecting to a remote browser session hosted on Kernelβs infrastructure.
### Quick start with Puppeteer
1. Remix this [Kernel starter val](https://www.val.town/x/onkernel/kernel_starter)
2. Create a free [Kernel account](https://onkernel.com/) and generate an API key from **Settings β API Keys**
3. Add it to your valβs Environment Variables (left sidebar) as `KERNEL_API_KEY`
4. Click **Run** on `puppeteer.ts`
5. View logs for output
### Quick start with Playwright
Kernel also supports Playwright sessions.
Steps are nearly identical:
1. Remix this [Kernel starter val](https://www.val.town/x/onkernel/kernel_starter)
2. Add your `KERNEL_API_KEY` to Environment Variables
3. Set `TMPDIR` to `/tmp` to avoid Playwright file-system warnings
4. Click **Run** on `playwright.ts`
5. Check the logs for output
---
# Steel
URL: https://docs.val.town/guides/browser-automation/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.
Since Val Town can't run browsers locally, Steel provides a way to run browser automation tools like Puppeteer and Playwright by connecting to remote browsers hosted on Steel's infrastructure.
## Quick start with Puppeteer
1. Remix this [Steel Puppeteer val](https://www.val.town/v/stevekrouse/steel_puppeteer_starter)
2. Get your free [Steel API key](https://app.steel.dev/settings/api-keys)
3. Add it to your Environment Variables (via left sidebar of your val) as `STEEL_API_KEY`
4. Click `Run` on the `main.tsx` file
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.
:::
## Quick start with Playwright
You can also use Playwright with Steel on Val Town.
Similar to the setup instructions above:
1. Remix this [Steel Playwright val](https://www.val.town/x/wolf/PlaywrightDemo)
2. Get your free [Steel API key](https://app.steel.dev/settings/api-keys)
3. Add it to your Environment Variables (via left sidebar of your val) as `STEEL_API_KEY`
4. Set `TMPDIR` to `/tmp` in your Environment Variables to avoid warnings
5. Click `Run` on the `main.ts` file
6. View the output in the logs
:::note
We use [a small script](https://www.val.town/x/wolf/StubFS/branch/main/code/stubFs.ts) to handle Playwright's file system requirements in Val Town.
Also, each Steel API key supports only one active browser session. Starting a new session will close any existing session.
:::
---
# PlanetScale
URL: https://docs.val.town/guides/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!
---
# Neon Postgres
URL: https://docs.val.town/guides/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).
---
# Supabase
URL: https://docs.val.town/guides/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).
---
# Upstash
URL: https://docs.val.town/guides/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!
---
# Get a Github user
URL: https://docs.val.town/guides/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;
};
```
---
# Github user's stars (pagination)
URL: https://docs.val.town/guides/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/guides/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](/reference/std/email/) to send yourself an email when someone
stars your GitHub repository, but you could also send a notification to
[Slack](/guides/slack/send-messages-to-slack/),
[Discord](/guides/discord/send-message/), or anywhere else.
First, create an HTTP trigger within a 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).
---
# Discord welcome bot
URL: https://docs.val.town/guides/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
Remix 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
Remix 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/guides/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 remix 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/guides/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

---
# ChatGPT Web/Desktop
URL: https://docs.val.town/guides/prompting/chatgpt.md
You can work with Val Town in ChatGPT (Web, Desktop) using our [MCP](#mcp) server.
## MCP
1. Open ChatGPT
2. Enable [developer mode](https://platform.openai.com/docs/guides/developer-mode) in Settings β Apps & Connectors β Advanced β Developer mode
3. Go to Settings β Apps & Connectors and add the Val Town MCP server
4. In a chat, choose Developer mode from the Plus menu and select the Val Town MCP server
Per ChatGPT's developer mode documentation, you can approve tool use within a conversation but not globally, so you'll need to authorize Val Town MCP for each chat.
:::note
ChatGPT's full MCP support is [only available](https://help.openai.com/en/articles/12584461-developer-mode-and-full-mcp-connectors-in-chatgpt-beta) for Business, Enterprise and Education accounts as of this writing.
:::
---
# Claude Code
URL: https://docs.val.town/guides/prompting/claude-code.md
Work with Val Town in Claude Code using our [MCP](#mcp) server or [CLI](#cli). You can ask Claude to:
- List or examine your vals and read your sqlite data, logs, and almost any of your Val Town data
- Edit your vals and even agentically iterate, viewing the output and logs until it's done
## MCP
1. Install [Claude Code](https://docs.claude.com/en/docs/claude-code/quickstart)
2. Install the Val Town MCP server:
```
claude mcp add --transport http val-town https://api.val.town/v3/mcp
```
3. Start `claude`
4. Run `/mcp` to view available MCP servers
5. Select `val-town`
6. Authenticate to Val Town by following the OAuth flow
:::tip
If you're feeling lucky, you can run `claude --dangerously-skip-permissions`
:::
:::tip
Use the [Playwright MCP server](https://til.simonwillison.net/claude-code/playwright-mcp-claude-code) to allow Claude Code to act as an agent, coding for you in a loop.
:::
## CLI
1. Install [Claude Code](https://docs.claude.com/en/docs/claude-code/quickstart)
2. Install the [Val Town CLI](https://github.com/val-town/vt)
```
deno install -grAf jsr:@valtown/vt
```
3. Copy-paste our [system prompt](https://www.val.town/x/valdottown/Townie/code/prompts/system_prompt.txt) into [`CLAUDE.md`](https://docs.claude.com/en/docs/claude-code/memory) at the root of your local directory for vals
4. `vt create`, `vt remix`, or `vt clone` a val\*
5. `cd` into your val's directory
6. (Optional) `vt checkout -b ` to isolate changes
7. `vt watch` to sync with Val Town
8. `claude` to start your Claude Coding session
_\* Running `vt create|remix|clone` will ask you to add editor files including `AGENTS.md`, which Claude doesn't support as of this writing (use `CLAUDE.md` instead)._
As an example, watch this 4-minute video [creating a customer support val](https://screen.studio/share/pYSiLKJU).
---
# Claude Web/Desktop
URL: https://docs.val.town/guides/prompting/claude.md
You can work with Val Town in Claude (Web, Desktop) using our [MCP](#mcp) server. You can ask Claude to:
- List or examine your vals and read your sqlite data, logs, and almost any of your Val Town data
- Edit your vals, and even agentically iterate, viewing the output and logs until it's done
## MCP
1. Open Claude
2. Go to [Connectors](https://claude.ai/settings/connectors) and add `https://api.val.town/v3/mcp` as a custom connector
3. Click "Connect" then "Allow" Val Town on the OAuth permission screen
4. Click "Configure" then configure all tool permissions you'll need
5. Go to [Capabilities](https://claude.ai/settings/capabilities), toggle "Allow network egress" and add `*.val.run` as an allowed domain
:::note
MCP servers ("Connectors") in Claude are [only available](https://support.claude.com/en/articles/11724452-browsing-and-connecting-to-tools-from-the-directory) for users with paid plans (Pro, Max, Team, or Enterprise).
:::
---
# 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()`.
:::
---
# 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!
---
# CLI
URL: https://docs.val.town/guides/prompting/cli.md
import { Steps, CardGrid, LinkCard } from "@astrojs/starlight/components";
Use the `vt` CLI to work with Val Town in your favorite IDE, e.g. with [Cursor](/guides/prompting/cursor).
`vt` is built with the Deno TypeScript runtime. To use vt, you need to make sure you [have Deno installed](https://docs.deno.com/runtime/getting_started/installation/).
## Setup
1. Install the [Val Town CLI](https://github.com/val-town/vt): `deno install -grAf jsr:@valtown/vt`
2. Run `vt` to login to Val Town
3. `vt create`, `vt remix`, or `vt clone` a val. Select βYesβ to add editor files\*
4. `cd` into your val's directory
5. (Optional) `vt checkout -b ` to isolate changes
6. `vt watch` to sync your changes to Val Town as you save files
_\* This will include `AGENTS.md`, which LLMs should respect (with some exceptions, like `CLAUDE.md`)._
## Popular tools
:::note
Are we missing your favorite AI tool? Click "Edit page" to update these docs, or [ask](https://discord.val.town/) [us](mailto:docs-help@val.town) to help.
:::
## Tips, Troubleshooting
- Ask to list or examine your vals or read sqlite data, logs, etc.
- Ask your LLM to edit vals and even agentically iterate
- Broken code? Visit the `Versions` tab or `History` of your val to revert
- Large changes? Use feature [branches](/vals/branches) and [merge](/vals/pull-requests) when stable
- Need help? Ask in the [Val Town Discord](https://discord.val.town/) in the `#local-dev` channel
:::caution
As always, review your LLM's code for risks like prompt injection. The more permissions you grant your LLM, the more it can do with your Val Town data (SQLite, blob storage, etc.).
:::
---
# Cursor
URL: https://docs.val.town/guides/prompting/cursor.md
## Setup
1. Install the [Val Town CLI](https://github.com/val-town/vt) and log in
2. Open [Cursor](https://cursor.com)
## Creating and editing vals
The most reliable way to write vals in Cursor is with `vt watch`, which will automatically sync your changes to Val Town as you save files.
1. `vt create`, `vt remix`, or `vt clone` a val. Select "Yes" to add editor files\*
2. `cd` into your val's directory
3. `vt checkout -b ` to isolate changes (optional)
4. `vt watch` to sync with Val Town
_\* Running `vt create|remix|clone` will prompt you to add editor files, including `AGENTS.md` with our [system prompt](https://www.val.town/x/valdottown/Townie/code/prompts/system_prompt.txt), which Cursor should recognize._
---
# GitHub Copilot
URL: https://docs.val.town/guides/prompting/github-copilot.md
## Setup
1. Install the [Val Town CLI](https://github.com/val-town/vt) and log in
2. Open [GitHub Copilot](https://github.com/features/copilot)
## Creating and editing vals
The most reliable way to write vals in VS Code with GitHub Copilot is with `vt watch`, which will automatically sync your changes to Val Town as you save files.
1. `vt create`, `vt remix`, or `vt clone` a val. Select "Yes" to add editor files\*
2. `cd` into your val's directory
3. `vt checkout -b ` to isolate changes (optional)
4. `vt watch` to sync with Val Town
_\* Running `vt create|remix|clone` will prompt you to add editor files, including `AGENTS.md` with our [system prompt](https://www.val.town/x/valdottown/Townie/code/prompts/system_prompt.txt), which GitHub Copilot should recognize._
---
# Overview
URL: https://docs.val.town/guides/prompting.md
import Val from "@components/Val.astro";
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
Bring Val Town to your preferred AI tool, or code on Val Town with Townie.
## Popular tools
:::note
Are we missing your favorite LLM? Click "Edit page" to update these docs, or [ask](https://discord.val.town/) [us](mailto:docs-help@val.town) to help.
:::
## Tips, Troubleshooting
- Ask to list or examine your vals or read sqlite data, logs, etc.
- Ask your LLM to edit vals and even agentically iterate
- Broken code? Visit the `Versions` tab or `History` of your val to revert
- Large changes? Use feature [branches](/vals/branches) and [merge](/vals/pull-requests) when stable
- Need help? Ask in the [Val Town Discord](https://discord.val.town/) in the `#local-dev` channel
:::caution
As always, review your LLM's code for risks like prompt injection. The more permissions you grant your LLM, the more it can do with your Val Town data (SQLite, blob storage, etc.).
:::
---
# MCP
URL: https://docs.val.town/guides/prompting/mcp.md
import { Steps, CardGrid, LinkCard } from "@astrojs/starlight/components";
Use the Val Town MCP server to work with Val Town in your preferred AI tool, e.g. with [Claude Code](/guides/prompting/claude-code#mcp).
## Setup
1. Connect the Val Town MCP server (`https://api.val.town/v3/mcp`) to your LLM
2. Authorize the Val Town MCP server by following the OAuth flow
E.g. for Claude Code:
```
claude mcp add --transport http val-town https://api.val.town/v3/mcp
```
In Claude, type `/mcp` to confirm that it's connected, and authenticate if necessary.
## Popular tools
:::note
Are we missing your favorite LLM? Click "Edit page" to update these docs, or [ask](https://discord.val.town/) [us](mailto:docs-help@val.town) to help.
:::
## Tips, Troubleshooting
- Ask your LLM to list or examine your vals or read sqlite data, logs, etc.
- Ask your LLM to edit vals and even agentically iterate
- Broken code? Visit the `Versions` tab or `History` of your val to revert
- Large changes? Use feature [branches](/vals/branches) and [merge](/vals/pull-requests) when stable
- Need help? Ask in the [Val Town Discord](https://discord.val.town/) in the `#local-dev` channel
:::caution
As always, review your LLM's code for risks like prompt injection. The more permissions you grant your LLM, the more it can do with your Val Town data (SQLite, blob storage, etc.).
:::
---
# OpenAI Codex
URL: https://docs.val.town/guides/prompting/openai-codex.md
## Setup
1. Install the [Val Town CLI](https://github.com/val-town/vt) and log in
2. Install [OpenAI Codex](https://developers.openai.com/codex/cli) and log in
## Creating and editing vals
The most reliable way to let Codex write vals for you is with `vt watch`, which will automatically sync your changes to Val Town.
1. `vt create`, `vt remix`, or `vt clone` a val. Select "Yes" to add editor files\*
2. `cd` into your val's directory
3. `vt checkout -b ` to isolate changes (optional)
4. `vt watch` to sync with Val Town
5. `codex` to start your Codex session
_\* Running `vt create|remix|clone` will prompt you to add editor files, including `AGENTS.md` with our [system prompt](https://www.val.town/x/valdottown/Townie/code/prompts/system_prompt.txt), which Codex should recognize._
---
# Townie
URL: https://docs.val.town/guides/prompting/townie.md
[Townie](https://townie.val.run) is our open source Val Town coding assistant.
Create vals with Townie's help, right in your browser. Townie itself is built as a val, so you can view its [source code](https://www.val.town/x/valdottown/Townie) and even remix it to your liking.

## Features
- 100% open source and built on Val Town
- Agentic AI reads & edits vals using Claude 4 Sonnet
- Support for multiple files, folders, & branches
- Instant deployments, auto-scaling, logs, free custom subdomains
- Works on mobile β prompt, build, and edit when youβre on the go
---
# Warp
URL: https://docs.val.town/guides/prompting/warp.md
## Setup
1. Install the [Val Town CLI](https://github.com/val-town/vt) and log in
2. Install [Warp](https://warp.dev)
## Creating and editing vals
The most reliable way to let Warp write vals for you is with `vt watch`, which will automatically sync your changes to Val Town.
1. `vt create`, `vt remix`, or `vt clone` a val. Select "Yes" to add editor files\*
2. `cd` into your val's directory
3. `vt checkout -b ` to isolate changes (optional)
4. `vt watch` to sync with Val Town
5. Use Warp's Agent Mode to create and edit your val
_\* Running `vt create|remix|clone` will prompt you to add editor files, including `AGENTS.md` with our [system prompt](https://www.val.town/x/valdottown/Townie/code/prompts/system_prompt.txt), which Warp should recognize._
---
# Windsurf
URL: https://docs.val.town/guides/prompting/windsurf.md
## Setup
1. Install the [Val Town CLI](https://github.com/val-town/vt) and log in
2. Open [Windsurf](https://windsurf.com/)
## Creating and editing vals
The most reliable way to write vals in Windsurf is with `vt watch`, which will automatically sync your changes to Val Town as you save files.
1. `vt create`, `vt remix`, or `vt clone` a val. Select "Yes" to add editor files\*
2. `cd` into your val's directory
3. `vt checkout -b ` to isolate changes (optional)
4. `vt watch` to sync with Val Town
_\* Running `vt create|remix|clone` will prompt you to add editor files, including `AGENTS.md` with our [system prompt](https://www.val.town/x/valdottown/Townie/code/prompts/system_prompt.txt), which Windsurf should recognize._
---
# Zed
URL: https://docs.val.town/guides/prompting/zed.md
## Setup
1. Install the [Val Town CLI](https://github.com/val-town/vt) and log in
2. Open [Zed](https://zed.dev)
## Creating and editing vals
The most reliable way to write vals in Zed is with `vt watch`, which will automatically sync your changes to Val Town as you save files.
1. `vt create`, `vt remix`, or `vt clone` a val
2. `cd` into your val's directory
3. `vt checkout -b ` to isolate changes (optional)
4. `vt watch` to sync with Val Town
_\* Running `vt create|remix|clone` will prompt you to add editor files, including `AGENTS.md` with our [system prompt](https://www.val.town/x/valdottown/Townie/code/prompts/system_prompt.txt), which Zed should recognize._
---
# Build a Slack bot
URL: https://docs.val.town/guides/slack/bot.md
import Val from "@components/Val.astro";
_Time to complete: 10 minutes_
This guide walks you through building a Slack bot that responds to mentions in channels.
If you just want to send simple Slack messages without interactivity, check out our [Slack webhook guide](/guides/slack/send-messages-to-slack/).
## Step 1: Remix the bot template
1. [Remix this val](https://www.val.town/x/charmaine/slackBotExample/code/main.tsx) to get started:
2. Copy the HTTP endpoint URL (via the ... menu for `main.tsx`) - this is where Slack will send events to your Val Town bot.
## Step 2: Create a Slack app
1. Go to [Slack API](https://api.slack.com/apps?new_app=1) and create a new app **From Scratch**
2. Name your app and select your workspace

## Step 3: Add environment variables
In your val's sidebar, add these **Environment Variables**:
- **slackVerificationToken**: From **Settings β Basic Information β Verification Token**
- **slackToken**: Leave empty (you'll fill this after installing)

## Step 4: Set up events
1. Go to **Features β Event Subscriptions**
2. Enable events and paste your val's HTTP endpoint in **Request URL**
3. Subscribe to **app_mention** under **Subscribe to bot events**

## Step 5: Set bot permissions
In **OAuth & Permissions β Scopes**, add:
- `app_mentions:read` (should already be there)
- `chat:write`

## Step 6: Install your app
Go to **Settings β Install App** and install to your workspace.

## Step 7: Add OAuth token
1. Copy the **Bot User OAuth Token** from **OAuth & Permissions**
2. Update your val's **slackToken** environment variable

## Step 8: Test your bot
1. Invite the bot to a channel

2. Mention the bot - it will reply!

## What's next?
You can find more Slack examples on our [Templates page](https://www.val.town/explore/use-cases).
---
# Sending Slack messages
URL: https://docs.val.town/guides/slack/send-messages-to-slack.md
_Time to complete: 5 minutes_
This guide walks you through sending your first Slack message from Val Town.
This guide uses [Slack's incoming webhooks](https://api.slack.com/messaging/webhooks) to send messages to a Slack channel. For more complex interactions, like responding to user messages or handling buttons, check out our [Slack bot guide](/guides/slack/bot/).
## 1. Create a Slack app & webhook
1. [Click here to create a new Slack app](https://api.slack.com/apps?new_app=1)
2. Click **From Scratch**
3. Type an **App Name**
4. Select your Slack workspace

5. Click **Incoming Webhooks** in the left sidebar of your app (under **Features**)
6. Toggle **Activate Incoming Webhooks** to **On**

7. Click **Add New Webhook** at the bottom of the page
8. Select your target channel
9. Copy the **Webhook URL** from Slack
10. (Bonus) Customize the **App icon** under **Basic Information** in the left sidebar ([Val Town icons](https://www.val.town/brand))
## 2. Add the webhook URL to Val Town
1. Click **Environment Variables** in your val's left sidebar
2. Add a new env variable with key: **SLACK_WEBHOOK_URL**
3. Paste the webhook URL you copied from Slack as the value

## 3. Send a message
Now you're ready to send your first message to Slack!
```ts title="Send message" val
import { IncomingWebhook } from "npm:@slack/webhook";
const slack = new IncomingWebhook(Deno.env.get("SLACK_WEBHOOK_URL"));
const EDIT = `<${import.meta.url.replace("esm", "val")}|(edit val)>`;
await slack.send(`π₯³ Hi from Val Town! ${EDIT}`);
```

We recommend including this link to the edit val, so everyone on your team is one click away from making changes.
## What's next?
Learn how to [make your message fancy with advanced formatting](https://api.slack.com/messaging/webhooks#advanced_message_formatting).
Take this a step further by responding to mentions, slash commands, buttons, and other user interactions with our full [Slack bot guide](/guides/slack/bot/)!
You can find more Slack examples on our [Templates page](https://www.val.town/explore/use-cases).
---
# Authentication
URL: https://docs.val.town/reference/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.
You can access your API token in your vals: `Deno.env.get('valtown')`
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 configure 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 exclude `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.
---
# Overview
URL: https://docs.val.town/reference/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.
---
# 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 registry 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.
---
# 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](/reference/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.
---
# JavaScript SDK
URL: https://docs.val.town/reference/api/sdk.md
import { LinkCard, CardGrid } from "@astrojs/starlight/components";
The Val Town TypeScript SDK lets you interact with our
[REST API](/reference/api) 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 [remixing 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 email
const me = await vt.me.profile.retrieve();
console.log(me.email);
// list some of your vals
const vals = await vt.me.vals.list({});
console.log(vals);
```
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'
}
```
---
# Basic examples
URL: https://docs.val.town/vals/http/basic-examples.md
import Val from "@components/Val.astro";
HTTP triggers expose a public endpoint.
The simplest endpoint returns a simple JSON message:
Or you could return HTML with the corresponding Content-Type header:
This echoes request headers back in the response:
We can get and return the request URL's query parameters:
You can also get the body of POST requests:
To obtain the HTTP endpoint, use the "Copy HTTP endpoint" button in the ... menu.
---
# 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 endpoints within your vals. 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/)
- Make sure you setup the Cloudflare proxy correctly: [Setup Cloudflare CDN](https://docs.val.town/vals/http/cdns/#cloudflare-cdn)
- [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).
---
# 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. Under SSL/TLS in Configure encryption mode set the SSL to Full (Strict). You could use other encryption modes but Flexible will cause issues.
a. Login to your Cloudflare dashboard
b. Go to SSL/TLS
c. Choose an encryption mode

4. Ensure the CDN configuration for that domain enables caching
5. 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.
6. Add `Cache-Control` headers in your val ([learn more](#cache-control-headers))
7. 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 frustrating 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 recommend 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 subdomains
URL: https://docs.val.town/vals/http/custom-subdomains.md
[HTTP endpoints](/vals/http/) within vals can claim custom subdomains
under `val.run`. To choose a custom subdomain:
- Click the green HTTP trigger dropdown
- Select "Custom subdomain"
- Enter your desired name and click "Save". Your endpoint will be available at
`https://.val.run`.

### 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. Some webhooks, like from Slack, require responses within a couple seconds, but you may need more time than that, ie if you're building an LLM Slack bot. We recommend accomplishing this today by sending an unawaited HTTP request to a val which does the longer processing.
### How to return early, and process after
```ts title="early-returning.ts" val
async function handle(req: Request) {
const new = new URL(req.url)
if (url.path === '/') {
// Send off the relevant data to /process below.
// This `fetch` is not awaited.
fetch(`${req.origin}/process`, {
method: "POST",
body: req.body,
});
// Respond immediately, before the processing is done
return Response.json("ok");
} else if (url.path === '/process) {
// Do the long processing here.
// await it to catch any errors and ensure it completes.
// Nobody is waiting on this request, so we can take as long as we need to.
await doSomeWork(request.body);
return Response.json("done");
}
return new Response("not found", { status: 404 })
}
```
### waitUntil
Other serverless platforms support
[waitUntil](https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil) to handle this case more explicitly. We are considering bringing this to Val Town.
### 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, 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 Lifecycle
When your HTTP file receives a request, we spin up a process to serve it. We keep that process alive for some time after the response is sent to be ready for quick follow on requests. After some inactivity with no new requests, your HTTP process is terminated. This means that if you fire off Promises without awaiting them, they may or may not finish before the process is killed, depending on how long they take and whether the process serving the HTTP endpoint is being kept alive to serve other traffic. The safest path is to `await` all `Promise`s, or handle long-running tasks in a new request, as explained above, which keeps the HTTP process alive until it returns.
---
# HTTP
URL: https://docs.val.town/vals/http.md
import Val from "@components/Val.astro";
import { LinkCard } from "@astrojs/starlight/components";
HTTP triggers let you serve a scalable API or website.
To add an HTTP trigger, click the `+` button in the top right of your val editor and select `HTTP`.

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/).
---
# HTML & JSX
URL: https://docs.val.town/vals/http/jsx.md
import Val from "@components/Val.astro";
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 file:
```tsx
/** @jsxImportSource https://esm.sh/react */
```
### React
We recommend using React:
### Preact
Preact is a great alternative to React:
### Vue
### Solid
### Hono
```
---
# 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 file 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 file.
### Hono
We typically recommend [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);
```
---
# Blob Storage
URL: https://docs.val.town/reference/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.
---
# Email
URL: https://docs.val.town/reference/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](/vals/email/)
:::
## Basic usage
```ts title="Example" val
import { email } from "https://esm.town/v/std/email";
await email({
subject: "Hello from Val Town",
text: "This is a test email sent from a Val Town val.",
});
```
### `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" }`.
3. We will soon allow you to email from any val email address you own, but in the meantime you can get around these limitations by using the `replyTo` field.
### `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",
replyTo: "your_custom_email@valtown.email",
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",
},
],
});
```
#### Attachment examples
- [Sending a PDF created in code](https://www.val.town/v/stevekrouse/sendPDF)
- [Receiving an attachment and then sending it along](https://www.val.town/x/stevekrouse/replyEmailWithAttachment/code/main.ts).
- [Receiving an attachment, sending it to OpenAI, and then emailing it](https://www.val.town/x/ValDotTownOrg/virtual-mail/code/main.ts).
### 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";
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://sdk.val.town/api/node/resources/emails/methods/send).
## Limitations
`std/email` is powered internally by Sendgrid.
---
# Proxied fetch
URL: https://docs.val.town/reference/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.
---
# OpenAI
URL: https://docs.val.town/reference/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.
Val Town free users can use any cheap model β where output is less than $1 per
million tokens β such as `gpt-5-nano`, `gpt-4.1-nano`, and `gpt-4o-mini`. Val
Town Pro users can make 10 expensive requests per rolling 24-hour window, and
are then bumped down to `gpt-5-nano`.
This SDK is powered by
[our openapiproxy](https://www.val.town/x/std/openaiproxy).
## 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-5-nano",
max_tokens: 100,
});
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();
```
---
# SQLite
URL: https://docs.val.town/reference/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.
---
# Migrations
URL: https://docs.val.town/reference/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
`);
```
---
# ORMs
URL: https://docs.val.town/reference/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.
---
# Usage
URL: https://docs.val.town/reference/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 },
},
]);
```
---