shiply.now

Site Data

Built-in storage for forms, waitlists & guestbooks — no backend needed

Site Data gives every published site a tiny built-in database. Declare typed collections in a manifest, and your pages can save visitor submissions with a relative fetch — no server, no external database, no API keys.

Starter manifest

Don't have a .shiply/data.json yet? Scaffold one:

shiply data init           # writes ./.shiply/data.json
shiply data init ./my-site # or target a specific directory

The default manifest declares a single messages collection (name, email, message), public-insert + owner-read, rate-limited to 10/hour/ip. Edit the fields to match your form, then publish — Site Data starts working immediately. init refuses to overwrite an existing .shiply/data.json; move or delete it first if you want to start over.

Manifest: .shiply/data.json

{
  "collections": {
    "signups": {
      "fields": {
        "email": { "type": "email", "required": true },
        "name": { "type": "string", "maxLength": 200 }
      },
      "access": { "read": "owner", "insert": "public" },
      "rateLimit": "10/hour/ip"
    }
  }
}
  • Field types: string, number, integer, boolean, email, url, datetime. Strings take maxLength; numbers take minimum/maximum.
  • Access per collection: read and insert, each "public" or "owner". Default: public inserts, owner-only reads — right for waitlists. Use read: "public" for guestbook-style pages.
  • rateLimit: "N/minute|hour|day/ip" for public inserts (default 10/hour/ip).

Submitting from your page

<form id="f">
  <input id="email" type="email" required placeholder="you@example.com">
  <button>Join waitlist</button>
</form>
<script>
  document.getElementById('f').onsubmit = async (e) => {
    e.preventDefault()
    const res = await fetch('/.shiply/data/signups', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ email: document.getElementById('email').value }),
    })
    alert(res.ok ? 'Thanks — you are on the list!' : (await res.json()).message)
  }
</script>

Records are validated against your field specs server-side; bad submissions get a 400 with a specific message ("email" must be an email address).

Reading records

  • Dashboard: every site page has a Data section — browse collections, delete records, export CSV.
  • Owner API (Bearer shp_ key): GET /api/v1/publishes/:slug/data/:collection?limit=50&cursor=...{ records, nextCursor } · DELETE /api/v1/publishes/:slug/data/:collection/:id
  • Public reads (only when read: "public"): GET /.shiply/data/:collection from the site itself.

CLI

The shiply data subcommands wrap the owner API for quick inspection, backups, and admin from your terminal.

shiply data list <slug>                                          # collections + counts
shiply data query <slug> <collection> [--limit 50] [--cursor <iso>] [--where '<json>']
shiply data insert <slug> <collection> --json '{"email":"a@b.co"}'
shiply data export <slug> <collection> [--out out.ndjson]        # streams NDJSON
shiply data wipe-collection <slug> <collection> --yes            # destructive

Notes:

  • All commands except data init and data insert need a Bearer API key (shiply login). data insert hits the public visitor endpoint — the manifest's access.insert decides whether it's allowed.
  • --where is a JSON object of equality filters (e.g. '{"email":"a@b.co"}'). Filtering is applied client-side, after server-side paging, so it sees the current page only — pair with --limit/--cursor for large collections or use export to scan everything.
  • data export streams NDJSON: one record per line, newline-delimited. Use --out <file> to write to disk; without it, the stream goes to stdout — pipe to jq or another file for ad-hoc analysis.
  • data wipe-collection requires the explicit --yes flag — there is no interactive prompt, so it's safe to script but unforgiving if mistyped.

Limits

LimitValue
Collections per site10
Fields per collection30
Record size16 KB
Records per collection25,000
Site must be ownedanonymous sites get 403 — claim first

MCP tools

When you mount the shiply MCP server (see Reference), four Site Data tools become available to your agent:

ToolInputReturns
data_list_collections{ slug }{ collections: [{ name, count }] }
data_query{ slug, collection, limit?, cursor? }{ records, nextCursor } — newest-first, limit ≤ 200 (default 50)
data_insert{ slug, collection, record }the visitor endpoint's JSON response — hits POST /.shiply/data/:collection, so access.insert applies
data_export_collection{ slug, collection, limit? }{ records, truncated } — buffered, limit ≤ 5,000 (default 1,000); truncated: true when the cap was hit

data_list_collections, data_query, and data_export_collection require owner auth (the MCP server uses your API key); data_insert follows the same public-or-owner rules as the visitor POST /.shiply/data/:collection endpoint.

For exports larger than 5,000 rows, fall back to the streaming CLI: shiply data export <slug> <collection> --out out.ndjson.