← ClawnifyCONTENT HISTORY

Update to Clawnify

Snapshot Nov 5, 2026 · 01:16 UTC · version 1.0.3

Collection source: downloaded plugin package.

WHAT CHANGED

First saved snapshot

No earlier snapshot is available to establish a change.

Save Clawnify and check future changes in Following.

Up to 10 plugins free · no card required.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Build and ship a Clawnify website — Astro on Cloudflare Workers, the built-in form system (never hand-roll a form), images and assets, and the draft-then-publish deploy flow. Use when creating or editing a Clawnify website, or when a site needs a contact/waitlist/signup form.",
  "included_files": [],
  "name": "build-a-clawnify-website",
  "skill_md_contents": "---\nname: build-a-clawnify-website\ndescription: Build and ship a Clawnify website — Astro on Cloudflare Workers, the built-in form system (never hand-roll a form), images and assets, and the draft-then-publish deploy flow. Use when creating or editing a Clawnify website, or when a site needs a contact/waitlist/signup form.\n---\n\n# Build a Clawnify website\n\nA Clawnify website is an **Astro** project deployed to Cloudflare Workers. One\nsite per organization. You write pages; the platform owns hosting, forms and\nthe publish flow.\n\n```bash\nnpx clawnify@latest init --type website --name \"My Site\" website\ncd website && pnpm install && pnpm dev     # http://localhost:4321\n```\n\nAlways invoke the CLI as `npx clawnify@latest <command>` — never a\nglobal or pinned install; stale CLIs miss deploy guards and fixes.\n\n```\nwebsite/\n  src/pages/          # one .astro file per route (index.astro → /)\n  src/layouts/        # shared shell: nav, footer, <head>\n  src/blocks/         # platform blocks you've adopted (see Forms below)\n  public/             # images, favicons — served as-is from /\n```\n\n## Forms: use the built-in system. Do not build your own.\n\n**This is the single most common mistake.** A site needs a contact or waitlist\nform, and the obvious move is to write a `<form>` and POST it somewhere — your\nown endpoint, a third-party form service, or the platform's submit URL with the\nwebsite id pasted in. All three are wrong, and the last one looks right enough\nto survive review.\n\nClawnify has a form system. It captures submissions, stores them, can email you,\nand can hand each one to an agent or a flow. You get all of it by using the\n**Forms block** rather than writing a form yourself.\n\n### How it works\n\n- The submit target is **computed at render time** from `WEBSITE_ID` — a\n  variable the platform bakes into your site's Worker — plus the form's id:\n\n  ```astro\n  const env = (Astro.locals as { runtime?: { env?: Record<string, string> } })?.runtime?.env;\n  const websiteId = env?.WEBSITE_ID ?? \"\";\n  const action = websiteId ? `https://provision.clawnify.com/forms/submit/${websiteId}/${formId}` : \"\";\n  ```\n\n  **Never hardcode the website id.** It is baked per Worker so a site can only\n  ever submit to itself — hardcoding throws that away, and the literal goes\n  stale the moment the site is recreated. An empty `action` in local `astro dev`\n  is expected: there are no Worker vars locally, so the form simply no-ops.\n\n- The submit script POSTs JSON with `Content-Type: text/plain;charset=UTF-8`.\n  That is not a typo — `text/plain` is CORS-safelisted, so the request needs no\n  preflight, which matters because the platform can't know your site's origin in\n  advance. The route parses the text body as JSON.\n\n- **Keep the Turnstile widget.** The ingest rejects a submission with no token,\n  so a form without the widget fails every time with \"Verification required\".\n  The sitekey shipped in the block is public and pairs with the platform's\n  secret; the widget will not validate on `localhost`, only on a real hostname.\n\n### Getting the block\n\nSites are seeded with the platform's block library at creation. If yours has\n`src/blocks/Forms/Contact.astro`, import it and go. If it doesn't — an older\nsite, or one scaffolded bare by the CLI — copy it in from the website template\nand adapt it.\n\n**The design is yours; the plumbing is not.** Restyle the markup, labels and\nclasses freely. Preserve, exactly:\n\n- the computed `action` (never a prop, never a literal),\n- the Turnstile widget,\n- the `data-clawnify-managed-form` attribute,\n- `export const schema`,\n- destructuring only known props — never splat leftover props onto `<form>`,\n  or an injected `action` becomes a way to point your form at someone else.\n\nThere is no submit script to copy. The runtime lives in the framework catch-all\n(`src/pages/[...path].astro`) and binds every `data-clawnify-managed-form` on\nthe page. If the block you inherited still has its own inline `<script>` and a\n`data-clawnify-form` attribute, it is the older shape: it still works, and you\ncan migrate it by deleting the script and renaming the attribute. Do not carry\nboth attributes on one form, and do not add `data-clawnify-form` to a managed\nform: older blocks bind that selector unconditionally, so the form would submit\ntwice.\n\n### After a successful submit\n\nTwo options, and the default is unchanged.\n\n- **Inline message** (default). The `successText` prop, rendered into the\n  status line.\n- **Redirect.** Set the `successUrl` prop, or `data-success-url` on any form\n  you hand-built. A site-relative path (`/thanks`) or an absolute `https://`\n  URL (a booking page is fine). Anything else is ignored and the message is\n  shown instead, which is what keeps `javascript:` out of the navigation.\n\nA redirect to a thank-you page is also the only way to count a form conversion\nin the analytics tool, which has no custom events: the dashboard Submissions\npanel counts submissions, but a pageview is what gives you a rate.\n\nNeed a side effect on success, like setting a cookie? Listen for the\n`clawnify:form-success` event on the form. It is cancelable, so\n`preventDefault()` suppresses the redirect if you would rather navigate\nyourself.\n\n### Fields\n\nThe stock block collects **name, email and message**. Two ways to go beyond\nthat, and the right one depends on whether your form is registered:\n\n- **Registered forms** (a tree node named `Forms/Contact`) are schema-locked:\n  the ingest 422s any field it doesn't expect. Give extra inputs **no `name`\n  attribute** and have the submit script fold them into `message`.\n- **Unregistered forms** — hand-built pages like this one — are not locked, so\n  extra fields post as themselves and are stored verbatim in the payload. Better\n  data, but if the site later moves onto the tree renderer, registration will\n  start rejecting them. Leave a comment saying so.\n\n### PDF attachments\n\nThe Contact block can collect one PDF per submission (job applications, RFQs).\nOff by default — enable it with the block's `attachment` prop (and optionally\n`attachmentLabel`), then **publish**: the file field is registered at publish\ntime, and the ingest rejects files on forms that haven't registered one. Rules\nthe platform enforces server-side (don't fight them client-side): PDF only,\nverified by content not by extension, max 10 MB. The submission's payload\ncarries the filename and a download link that only the site's own organization\ncan open — the file is never on a public URL. When a file is attached the\nsubmit script posts `multipart/form-data` instead of JSON; both are\npreflight-free, and the block already handles the switch.\n\n### Where submissions go\n\nEvery submission is stored and visible in the dashboard. From there it can\nnotify an email address, or fan out to an agent or a flow — which is how a\nsignup becomes a CRM record or a WhatsApp message without you writing a backend.\nSet the notification address in the dashboard, not in the site.\n\n## Images and assets\n\nPut them in `public/` and reference them with an absolute path (`/img/hero.jpg`).\nThey're served as static files. Pull in real assets at real resolution rather\nthan linking someone else's CDN from your markup.\n\n## Deploying: draft first, then publish\n\n```bash\nnpx clawnify@latest deploy   # → the DRAFT site, visible only to your org\n```\n\nDeploying **never** changes the live site. Going live is a separate, explicit\nstep: review the draft, then **Publish to live** in the dashboard. Tell the user\nthis — a deploy that \"worked\" but didn't appear on the live site is almost\nalways someone expecting deploy to publish.\n\n## Pinning the org\n\nEvery command resolves which organization it targets, folder-first. In a linked\nfolder you get it for free; otherwise pass `--org`. If you work across several\norganizations, pass `--org` explicitly on anything that writes — the global\nactive org is shared machine-wide and another session can change it under you.\n"
}

SHA-256 of public snapshot: f1f517442113e694cace6ad01027a54aaa348cc6ebad8e6bac657180615b38f6