Skip to content

Editable content and forms

Let anyone in the workspace edit a website’s words and pictures in Studio, and collect form entries there.

Your website declares which words and pictures are editable and which forms it takes. Prices and opening hours then change without a deploy, order requests arrive in Studio, and the files stay as you built them.

Declare content and forms

Put a benchy.site.json file at the top of your output folder, next to index.html; Benchy reads it on each deploy. Content is grouped in collections: one set of fields, such as the shop’s details, or a list of items with the same fields, such as prints.

benchy.site.json
{
  "content": {
    "prints": {
      "label": "Prints",
      "list": true,
      "fields": {
        "title": { "type": "text", "label": "Name", "required": true },
        "price": { "type": "number", "label": "Price" },
        "photo": { "type": "image", "label": "Photo" },
        "inStock": { "type": "boolean", "label": "In stock" }
      }
    },
    "shop": {
      "label": "Shop details",
      "fields": {
        "hours": { "type": "text", "label": "Opening hours" },
        "about": { "type": "longtext", "label": "About us" }
      }
    }
  },
  "forms": {
    "order": {
      "label": "Order requests",
      "fields": {
        "name": { "type": "text", "required": true },
        "email": { "type": "email", "required": true },
        "print": { "type": "select", "options": ["Benchy", "Planter", "Lamp"] },
        "message": { "type": "longtext" }
      },
      "redirect": "/thanks/"
    }
  }
}
  • Names of collections, forms and fields use letters, digits, - and _, and are case-sensitive. id is reserved, because every list item carries its own id. Each label is what Studio shows.
  • A new field starts empty. A removed field is hidden and its values kept, so rolling back to a version that had it brings them back.
  • A deploy without the file declares no content or forms, so /_benchy/content/ answers 404 until a deploy that has it; nothing is deleted.
  • A deploy with an invalid file is refused, naming the setting, such as content.prints.fields.photo.type. Only a JSON syntax error gives a line number.

Field types

TypeHoldsUsed in
textOne line, up to 500 charactersContent and forms
longtextPlain paragraphs, up to 10,000 charactersContent and forms
numberA numberContent and forms
booleanYes or no; a checkbox in a formContent and forms
dateA calendar date, as 2026-10-03Content and forms
emailAn email addressContent and forms
urlA web addressContent and forms
selectOne of the listed optionsContent and forms
imageA picture with its alt text, up to 25 MiBContent only

Edit content in Studio

On the website’s page, Content shows one form per collection. Anyone with Can edit changes the words, adds, reorders and removes items, and uploads pictures, with no deploy. After Save, visitors get the change within seconds.

  • Content isn’t part of a version: rolling back keeps today’s content.
  • Content text counts against the website’s 16 MiB content limit; uploaded pictures count toward the workspace’s storage.
  • Each save is in the website’s history, with who made it.

Screenshot: the Content panel in Studio, one form per collection.

Use content in your pages

Each collection is served on your website at /_benchy/content/<collection>.json. A list is { "$revision": n, "items": [...] } in Studio’s order, each item with its own id; a single collection is { "$revision": n, ...fields }. A picture is an object with a url on your website, width and height in pixels, and alt. An empty field is absent rather than null. $revision rises on every change.

js
const { items } = await (await fetch("/_benchy/content/prints.json")).json();
// [{ "id": "k3m9zq1x", "title": "3DBenchy", "price": 12, "inStock": true },
//  { "id": "p7w2bb0d", "title": "Amber ring",
//    "photo": { "url": "/_benchy/content/files/….png", "width": 1200, "height": 900, "alt": "…" } }]

const list = document.querySelector("#prints");
for (const print of items) {
  const item = document.createElement("li");
  if (print.photo) {
    const { url, width, height, alt } = print.photo;
    item.append(Object.assign(document.createElement("img"), { src: url, width, height, alt }));
  }
  item.append(Object.assign(document.createElement("h3"), { textContent: print.title }));
  list.append(item);
}
  • It is your website’s own address, so there is nothing to configure.
  • A collection the live version doesn’t declare answers 404, as does a picture the content no longer shows.
  • A preview reads the live website’s content, shaped by the preview’s own benchy.site.json: a field it adds shows empty, and one it removes isn’t served.
  • For pages that must work without JavaScript: pull the content, build, deploy.

Content from the CLI and agents

shell
benchy site content pull olive-prints   # writes content/prints.json and content/shop.json
benchy site content push olive-prints   # saves your edited files as the website's content

pull writes one file per collection, in the shape above; push saves them back. An agent can read and change content if its key or connection can make changes in the workspace; a Publish websites key only reads it.

Add a form

Declare the form in benchy.site.json, then post an ordinary HTML form to /_benchy/forms/<name> and include Benchy’s form script:

html
<form method="post" action="/_benchy/forms/order">
  <label>Name <input name="name" required></label>
  <label>Email <input name="email" type="email" required></label>
  <label>Message <textarea name="message"></textarea></label>
  <button>Send</button>
</form>
<script src="/_benchy/forms.js" defer></script>
  • The script adds a spam check and a hidden trap field, sends the entry, and shows the answer beside the button. It sets no cookies and works on a custom domain too. A form posted without it is refused.
  • After a good entry the visitor goes to the form’s redirect page, or back to the same page with ?sent=order.

A form on a canvas

On a canvas, choose Create, then Elements, Forms, Contact form. It adds Name, Email and Message fields and a Send button, each an ordinary layer you can restyle.

  • Select the form or a field and open Website in the top bar. A form has a Name (such as contact) and Sent, the line a visitor reads after sending. A field has a Type, Name, Label and Required. Any rectangle inside the form can become a field or the send button.
  • A field’s text is its placeholder; its Label is read by screen readers and names its column in the entries.
  • The form takes entries once the canvas is published as a website; Benchy declares its forms for you.
  • On an Instant link and in Preview, the form shows but takes no entries, and says so.

Read entries

Forms on the website’s page lists each form’s entries, newest first, with the page each came from. The owner and editors see them; viewers don’t, because entries hold people’s details.

  • Export CSV downloads every entry, or those between two dates.
  • Delete an entry any time; entries are deleted automatically after 90 days.
  • One export holds up to 10,000 entries, the newest; the CLI and agents say when there were more, and an earlier --until reads the rest.
  • Entries count toward the workspace’s monthly quota. See Plans.
shell
benchy site forms list olive-prints                                # each form's name and this month's entries
benchy site forms export olive-prints order > orders.csv
benchy site forms export olive-prints order --since 2026-09-01 > september.csv

Tell your visitors what you collect and why, on the page with the form.

Email notifications

Each member chooses Email me new entries on a form for themselves. The email is plain text with the form’s fields and the website’s address; replying answers the entry’s email field, when there is one. A website sends at most 20 an hour, then one hourly summary of the rest.

What forms accept

  • Declared fields only, checked against their types and required; anything else is dropped.
  • No passwords, payment details or government ID numbers: a deploy declaring a password field, or fields for a card number, expiry date, security code or government ID number, is refused.
  • One visitor can send 10 entries a minute to a site. Entries that fail the spam check are dropped and don’t count.
  • At the monthly quota, on any plan, forms answer “This form isn’t taking entries right now” until the next month.

Limits

LimitValue
Collections per website20
Forms per website20
Fields in a collection or a form40
Options in a choice field50
A label80 characters
Items in a list1,000
One item16 KiB
Links in one item50
One collection’s content4 MiB
All content on a website16 MiB, pictures not included
One entry32 KiB of text, no files