GoSX Studio is the web-editing admin portal for no-code, user-authored websites. It is the entire surface a site owner lives in day to day: canvas design, content, commerce, media, and settings back-office, all in one GoSX-rendered shell. Think Webflow-shaped — host-configured, GoSX-rendered, one editor, one back-office, one portal.
Reference deployments: Muddy Noni (commerce) and Pajaritos (school site).
go run m31labs.dev/gosx-studio/cmd/gosx-siteOpen http://127.0.0.1:8080/admin and a three-question wizard asks what the
business is called, what it does, and how people reach it. It then builds a
real, published website — a bakery gets a menu and a visit page, a consultant
gets services and about — which you edit and publish from there. Nothing else
is required: no adapters, no host application, no configuration file.
Visit your site http://127.0.0.1:8080/
Edit your site http://127.0.0.1:8080/admin
Saved in ./data/site.db
Editing happens on the page itself. The canvas renders the same markup a
visitor sees; click any text to change it, use the hover controls to move,
copy, or delete a section, and press + between sections to add one. Changes
save as you type and stay private until you press Publish — a live page keeps
serving its published version while you work on the next one.
The server binds to localhost by default and refuses to listen on a public
address unless you set -admin-password, so it never exposes an unprotected
admin area by accident.
Every site gets a working contact form on its contact page. Messages land in the admin area's inbox — no mail server to configure.
On a laptop with no -admin-password, the admin is open, as before. On a
server, the password you start with unlocks one thing: the form at
/admin/login that creates the owner account. After that, everyone signs in
with an email and password, and the password on the command line is only a
bootstrap.
People in the admin invites others by link, as an editor (writes and publishes, reads messages) or an admin (everything except handing over the site). Anyone can turn on two-step sign-in with an authenticator app under Account. Activity shows who did what, and can be downloaded.
Settings → Single sign-on connects an OpenID Connect provider such as
Google Workspace, Microsoft 365, Okta, or Keycloak. Register the site there
with the redirect address https://yoursite.com/admin/sso/callback, paste
the issuer address, client ID, and secret, and a "Sign in with…" button
appears on the sign-in page. Someone whose email already has an account
signs in as that account; someone from the allowed email domain gets an
editor account; everyone else is turned away.
There is no SAML on the site's side, on purpose. A company that runs SAML (Okta, Entra ID, OneLogin, Keycloak) publishes the same directory as an OpenID Connect application in a few clicks, and that is what you connect.
A site run on its own has everything. A platform that hosts many sites
starts each one with -features (or GOSX_SITE_FEATURES), a list such as
blog,forms,stats,domain,team, and the parts left out disappear from the
admin menu and answer with a short "not part of your plan" page. The names
are blog, forms, shop (products, orders, bookings, downloads,
subscriptions), stats, domain, team, staging, and sso.
A platform also sets -managed-by (its name), -domain (the owner's
domain it connected), and -operator-token. The site then tells its owner
to ask the platform about domains instead of walking them through DNS, and
answers GET /platform/status (JSON) and GET /platform/export.zip (the
full backup) to callers who present the token as a bearer token.
Turn on Settings → Team → Publishing needs approval and editors' Publish button becomes Request review. Requests wait under Review, where an admin looks at the draft as visitors would see it, then approves and publishes it or sends it back with a note the editor sees in the editor. Anyone signed in can make a preview link for a draft from the editor's sidebar: it shows the current draft to whoever has it for three days, without an account.
- Locked sections. An admin can lock any block from its toolbar. Editors see it and cannot change or remove it; a save that touches a locked block is refused with an explanation.
- Request logs and metrics. Every response carries an
X-Request-ID, and each request is one JSON line on stderr (-log-requests=falseto stop)./admin/metricsserves Prometheus counters and content gauges to a signed-in admin. - Privacy. Messages export as CSV, delete one by one or by age, and can expire automatically (Settings → Privacy). One click writes a plain-language privacy page for the owner to check and publish.
Open Settings → Your own domain in the admin. Type the domain, add the two DNS records the screen shows, and press Check DNS now until it reports that the domain reaches the server.
For HTTPS, start the site with the -https flag. It then listens on ports 80
and 443, fetches a certificate from Let's Encrypt the first time someone opens
the secure address, and renews it on its own:
gosx-site -https -data /srv/site/site.db -admin-password 'a long passphrase'Certificates are cached in a certs folder beside the data file. Pass
-public-ip to show the server's address in the DNS instructions when it
cannot be detected.
Everything is in one SQLite file, data/site.db by default, with pictures in
an uploads folder beside it. A site that was started on an older version
with data/site.json is moved into the database on the next start, and the
JSON file is kept as site.json.migrated. Pass a .json path to -data to
keep using the one-file snapshot instead.
Shop in the admin holds products with pictures, options, and stock. A
product on sale appears at /shop and in the menu, and can be placed on any
page with the Product block. Visitors fill a cart; to let them pay, connect
Stripe under Settings → Payments:
- Paste your Stripe secret key.
- In Stripe, add a webhook endpoint for
https://yoursite.com/stripe/webhooksendingcheckout.session.completed, and paste its signing secret. - Set a flat shipping charge, a free-shipping threshold, the countries you ship to, and whether Stripe Tax works out tax.
Payment happens on Stripe's hosted page; card details never reach the site. Paid orders appear under Shop → Orders with the buyer's address, a link to the payment in Stripe for refunds, and a "Mark as sent" button.
A product is one of four kinds. Something you ship is the default. A download takes a file up to 100 MB; the buyer gets a link that works for seven days, on the thank-you page and in the receipt. A subscription charges every week, month, or year through Stripe; renewals and cancellations flow back through the webhook. An appointment offers time slots from the days, hours, and slot length you set; free appointments book without checkout, paid ones go through the cart. Bookings appear under Shop → Bookings, where you can cancel one.
Buyers have no passwords. The Your orders link in the footer asks for
their email and sends a sign-in link good for thirty minutes; the page then
shows their orders, downloads, and subscriptions, with a button that opens
Stripe's billing portal to change or cancel a subscription. A visitor who is
not ready can ask the cart page to email them a link to their cart; if they
have not bought within two hours, one reminder goes out, and never a second.
Both need an email transport (-mail).
Beside the six palettes there is Custom: pick a background and a text colour and the rest of the palette is mixed from them. Headings can be quiet, regular, or big; the text column narrow, regular, or wide. Every picture has a size (full, wider than the text, medium, small), a shape (as it is, wide crop, square, round), a caption, and a link. For owners who know CSS, Settings → Your own CSS applies to every page and to the editor's canvas; imports and anything that isn't plain CSS are stripped.
Everything the editor can do, an agent can do too. The site is agent-native in four ways.
Reading. Every public page is also Markdown: add ?format=md or send Accept: text/markdown. /llms.txt describes the site (pages, contact, shop) and /llms-full.txt carries every page as one Markdown file. No key is needed.
Editing. An admin makes a key under Agents (name it after what will use it, tick what it may do: read, write, publish, settings). With the key, an agent uses the same operations as the editor:
- the JSON API at
/agent/v1(OpenAPI at/agent/v1/openapi.json, block schema at/agent/v1/schema); - MCP at
/agent/mcp(Streamable HTTP, bearer key). Claude Code:claude mcp add --transport http my-site https://yoursite.com/agent/mcp --header "Authorization: Bearer gsk_…"; - a stdio bridge for clients that lack HTTP MCP:
gosx-site mcp -site https://yoursite.com -key gsk_….
Pages are lists of blocks in the shape the editor uses. PATCH /agent/v1/pages/{id} edits by index (replace, remove, insert, append, move); PUT replaces; POST /agent/v1/pages creates from a template or from blocks. Posts, products, pictures, the Look, presets, messages, and visitor counts have calls of their own. Every write is a draft until publish, every action is audited under the key's name, and a revoked key stops at once.
Building from nothing. A platform can pass -agent-key gsk_… (or GOSX_SITE_AGENT_KEY) so an agent can call POST /agent/v1/setup and build the whole site before anyone signs in.
Your browser's assistant (WebMCP). The editor and every admin page announce their tools to the browser through WebMCP. A browser with a built-in assistant can then work the editor for you: "add a pricing section with three plans", "publish this page", "make the look warmer". The tools act as you, with your permissions, and every change is a draft you can undo from History. Browsers without an assistant ignore them.
A new site's first visit to /admin is a six-step wizard, and the answers become the site:
- Your business. Name, a one-line tagline (search results and shares), and two or three sentences about you (the About page and the hero).
- What you do. Food, services, shop, portfolio, community, or something else. This picks the pages and the words.
- What you offer. Three things, each with a line and an optional price. They become feature cards on the home page, the menu or services page, and a pricing page when priced.
- Where and when. Email, phone, address, opening hours (a plausible week to correct, or "we don't have hours"), Instagram and Facebook. They fill the contact page, the hours and map sections, the footer, the header button (Call us / Email us), and what Google shows.
- Your look. Three starting points for colours and type.
- Your pages. What a business like yours usually needs is ticked: Home and Contact always, the kind's own page, About, a privacy policy kept out of the menu; Photos, Questions, and Pricing to opt into. "Publish everything now" is on; untick it to keep drafts.
On a hosted site the wizard is followed by the one-time password from the welcome mail, which creates the owner's account. Then the dashboard opens on Your site is ready: every page with its state and an Edit link, the live address, and the next few things to do.
Any section band and any hero can carry a backdrop: a generative picture drawn live on a canvas behind the words. Eight kinds: Aurora (soft drifting colour), Particles (drifting points that link up), Waves, Orbs (lit spheres in depth), Grid (a floor to the horizon), Stars (a parallax field), Contours (a slowly breathing map), and Ribbons (twisting bands in depth). There is nothing to download: they draw from your own palette, so changing the Look changes every backdrop.
Every backdrop has a variation. New sites get one seeded from their name, so no two sites look alike and yours always looks like itself. Shuffle gives you another. Each has Motion (moving, slow, still), Strength (subtle, normal, bold), and a band can add Depth, which makes its cards lean toward the pointer.
Two hero layouts are built for them: Poster (big type over a full backdrop) and Stage (centred on a floor).
In the Look, Motion sets the whole site to full, calm, or off. Visitors who ask their device for less motion always get a still picture, tabs in the background stop drawing, and a big screen never costs more than it should.
Agents see the same choices in the schema (fx, motion, intensity, seed, depth on a section; effect, motion, intensity, seed on a hero).
Do anything. Press Ctrl+K (or click the box at the top) and type what you want: "pricing" adds a pricing section, "publish" publishes, "visit" opens the Visit page, "look" jumps to colours and fonts. Arrow keys choose, Enter does it.
Find a section. The sidebar has a search box; type "hours" or "map" and press Enter to add the first match.
Keyboard. Press ? for the sheet. Alt+↑ and Alt+↓ move the section you're in. Delete removes the selected section and offers Undo instead of asking "are you sure". Esc steps out of the text to the section. Ctrl+S saves now, Ctrl+Shift+P publishes.
Know where you are. Hover any section and its name shows at the top left: Hero, Pricing, Text. Every link field offers the site's own pages as you type.
Preview before you publish. The Preview button opens your draft exactly as visitors will see it, with a banner and a way back. View live opens what they see right now.
On a phone. The editor fits a phone: the sidebar becomes a drawer behind the + Add button, and every tool works with a thumb.
First time. Three tips sit above the page until you say Got it.
Every card, plan, question, person, row, and gallery picture has a small toolbar when you hover it. Drag the ⠿ handle to move it, or use the ↑ and ↓ buttons. The page saves on its own.
Every block has a space choice in its toolbar: tight, normal, roomy, or extra room. Use it to pull two blocks together or to give a section air.
Got a section right? Click ★ Save as a preset and give it a name. It appears under Your presets in the sidebar of every page. Adding a preset drops in a copy; the copy is yours to change. Forget a preset with ✕; pages that used it keep their copies.
Press Ctrl+S (⌘S on a Mac) to save at once.
Pages carry the polish a visitor expects: a skip link for keyboard users, visible focus rings, smooth scrolling that respects reduced-motion settings, a browser theme colour that matches the Look, a print stylesheet, and FAQ rich results for search engines when a page has a questions section.
Settings → Brand now shapes the whole frame: an announcement bar across the top with an optional link, a standout button at the end of the menu ("Book a table"), a header that stays put while scrolling, the menu repeated in the footer, extra footer links, and social profiles drawn as icons. In the editor, "In the menu" on any page tucks it under another page as a drop-down; menus stay one level deep on purpose.
The editor's sidebar starts with whole sections, not just pieces: a hero, feature cards, testimonials, pricing, questions and answers, a call to action, numbers, people, opening hours, a picture beside text, a map, and plain space. Each drops in with sensible starter copy, is edited in place like everything else, has a Layout switch (centred, split, cards, one big quote, and so on), and repeated things (cards, plans, questions, people) have an add button and a remove button.
A section break sets the mood for everything beneath it: a plain, tinted,
accent, dark, or picture background; left or centred text; narrow, normal,
wide, or edge-to-edge width; compact, normal, or roomy spacing; and a
jump-to name, so a button can link to #pricing.
Small things are adjustable too. Select any text and the floating toolbar aligns it left, centre, or right. A button can be solid, outlined, or just a link. Columns come in two or three. "Latest posts" and "From the shop" sections fill themselves from the blog and the shop and stay current.
Two people can open the same page. Each sees the other's initials in the editor's top bar, and when one of them saves, the other's canvas refreshes with the new blocks. If that person is typing, the refresh waits for a pause and a note says who changed what. There is no merging: the last save wins, and History keeps every version.
Saving in the editor never changes what visitors see; publishing does. The Staging page lists every page and post with unpublished changes and publishes them one at a time or all together with one button.
To walk through the whole site with those changes in place, give it a
staging address such as staging.yourbusiness.com on the Staging page and
point that name at the server like the main domain. The staging address
shows drafts, menus and all, under a banner. It opens only for people who
have the link, which carries a key you can renew. Search engines are told to
stay away, nothing is counted, and anything that writes or pays is sent to
the real site.
Settings → Backups and export downloads the whole site as one zip: pages,
posts, pictures, messages, forms, and visitor counts. A backup of the same
kind is written once a day into a backups folder beside the data file, and
the last fourteen are kept. Turn that off with -no-backups.
To restore, stop the site, unzip the backup, and start the site from the
site.db inside it. Certificates are never included; HTTPS issues new ones.
docker build -t gosx-site .
docker run -p 8080:8080 -v gosx-site-data:/data \
-e GOSX_SITE_ADMIN_PASSWORD=change-me gosx-sitePages, pictures, and messages all live in the /data volume. The admin
password is required in a container because the server is reachable from the
network; the image will not start without one.
cmd/gosx-site is the default host: it assembles the sitehost, cms, and
hostruntime packages into a program that runs. Applications that need more
control still configure the packages directly, as the reference deployments
do. The default host is a floor, not a ceiling.
Earlier docs described Studio as "the authoring layer, intentionally separate
from gosx-cms and gosx-admin." That framing is retired — the code moved
past it. Studio is the portal; gosx-admin is a generic back-office toolkit
dependency (Studio consumes only gosx-admin/blockstudio); the former
gosx-cms content-storage module is folded into this module as cms/* (see
docs/ARCHITECTURE.md §"Release model" and
cms/PROVENANCE.md for the fold-in record). The standalone
m31labs.dev/gosx-cms repository is frozen and tombstoned as of its final
v0.2.1 tag; new code should import m31labs.dev/gosx-studio/cms/....
Hosts supply adapters, persistence, permissions, routes, and copy. Studio supplies contracts, chrome, panels, canvas engines, back-office pages, and runtime islands.
Concretely, the host application supplies:
- content adapters
- shell labels, modes, panels, and resource links
- permission adapters
- server actions
- route bindings
- product copy
- feature flags
- design tokens
Studio supplies:
.gsxsurfaces for visible editor UI- GoSX engines for heavy client-side interactions
- GoSX islands for focused reactive controls
- extension points for plugins
- common authoring language for non-technical operators
The module is a strict import DAG: core sits at the bottom, shell
composes everything above it, and peers never import each other. See
docs/ARCHITECTURE.md for the full diagram.
| Package | Responsibility |
|---|---|
core |
Pure data contracts and defaults: the site-map/canvas/composition/flow/engine/resource type system, zero UI or host imports. |
authoring |
The typed server-action mutation boundary: operations, form codec, adapter interface, action handler, authoring surface assembly, and the style/appearance/section-field mutation family. |
hostruntime |
Embedded runtime assets, bundle concatenation, and the public /_gosx/studio/* paths and HTTP mounting. |
canvas |
Page-canvas engine hosting and server-rendered surface markup (artboards, thumbnails, block-layout DOM contract). |
sitemap |
The visual site-map board: graph engine render, board/view projections, authoring panels/forms, site navigator. |
panels |
Editor inspector/designer panels and their .gsx islands (right/left rail content units). |
backoffice |
The CRUD portal surfaces — dashboard, per-domain index/detail pages, media library, settings, search, storefront preview. First-class per the portal definition. |
shell |
The portal shell: host-facing ShellConfig/Shell/Store contracts, readiness rail, workbench chrome renderers, and the editor workbench composition root. |
*runtime islands (fieldruntime, selectionruntime, workbenchruntime, styleruntime, previewruntime, blocklayoutruntime, brandruntime, inspectorruntime, sitemapruntime, authoringruntime, inlineeditruntime, canvas*runtime) |
The go:embed browser-runtime bundles the shell/canvas/sitemap/panels packages mount — one package per engine's client-side JS/WASM surface. |
plugins/showcase3d |
The CMS/Studio contract for source photos, generated model artifacts, provenance, moderation, lifecycle readiness, no-code placement controls, and Scene3D viewer descriptors. |
studio (root) |
Deprecated compatibility facade — type aliases and forwarding wrappers only, no logic. See "Versioning & compatibility" below. |
cms/{blocks, content, flows, lifecycle{,/sqlstore}, media, render, store{,/file,/memory}, style} |
Folded-in content storage: block catalogs, revision/draft/publish lifecycle, media assets, generic block rendering, and CMS store contracts (+ in-memory/file/sqlite-backed implementations). Bottom tier — stdlib, gosx, gosx-admin only, zero imports of cms/studio or the root facade. See cms/PROVENANCE.md. |
cms/studio (+ cms/studio/collab) |
Folded-in portal UI (three-pane authoring shell model, canvas/preview/panels/actions, realtime collaboration) that used to live in the standalone gosx-cms module. Top tier — imports cms/{lifecycle,flows,style} and cms/studio/collab; nothing in this module imports cms/studio back (that would close a cycle through the facade — see ARCHITECTURE.md). Ships its own forked runtime assets — see "Release model" below. |
- Mount the embedded runtime assets and stylesheet:
hostruntime.MountRuntimes/hostruntime.DefaultRuntimeConfig. - Configure the portal shell:
shell.ShellConfig/shell.DefaultShellConfig(labels, modes, panels, resource links, engine globals, feature flags). - Wire the mutation boundary:
authoring.AuthoringActionHandleragainst yourauthoring.AuthoringAdapterimplementation. - Render the editor route with
shell.RenderBackendEditorPage, and the CRUD routes with thebackofficepackage's per-domainRender*pages.
The canonical host adapter is muddy-noni-commerce's
internal/studiohost/adapter.go, which builds a hostruntime.RuntimeConfig
and shell.ShellConfig (host labels, resource links, panels, actions) end to
end.
The root studio package is a deprecated compatibility facade for one
release cycle (v0.6.x): every symbol muddy, pajaritos, and cms/studio (the
former gosx-cms/studio) used before the package restructure still resolves
through a type alias or thin forwarding wrapper, each marked // Deprecated:.
New code should import the subpackages above directly (core, authoring,
shell, canvas, sitemap, panels, backoffice, hostruntime) rather
than the root.
The gosx-cms module fold-in (previously "Phase 2") landed: all 13 gosx-cms
packages now live under cms/ in this module, and gosx-studio's own
go.mod no longer requires gosx-cms — see
docs/ARCHITECTURE.md §"Release model" for the folded
DAG shape and cms/PROVENANCE.md for the path mapping and
source commit.
See docs/WEBFLOW_CLASS_EDITOR_GAP_INVENTORY.md for the detailed gap
inventory and work plan toward a Webflow-class no-code editor.