Digylearn — Build & Publish Guide (MCP)
This guide walks through building and publishing a complete site using the Digylearn MCP tools. All tools run authenticated as the site owner; tenant/site resolution happens automatically from your account.
Typical build flow
digy_create_site -> digy_write_page_code -> (digy_create_collection) -> digy_publish_site
digy_write_page_code is the recommended default for authoring pages (full HTML + CSS + JavaScript, code mode). Use digy_update_page only for simple static content. You can repeat page-authoring, digy_create_collection, digy_create_record, and asset tools as needed before publishing. A site only becomes publicly reachable after digy_publish_site.
1. Create a site
digy_create_site({ "name": "My Portfolio", "slug": "my-portfolio" })
The slug must be unique within your workspace and alpha_dash (letters, numbers, dashes). The site starts unpublished.
2. Write pages
There are two ways to author a page. write_page_code (code mode) is the recommended default for building a real site — it supports full HTML + CSS + JavaScript and is not subject to the sanitized-mode restrictions. Reserve update_page (sanitized mode) for simple content-only pages where you are certain no JavaScript, class-based CSS, or forms are needed.
Why prefer code mode? Sanitized mode strips
<script>,<style>,<form>, andon*handlers on render, so any page that later turns out to need interactivity must be rebuilt from scratch — wasting time and context. Starting in code mode avoids that rework.
2a. Interactive pages — write_page_code (code mode) — recommended default
For pages that need client-side logic — carts, tabs, modals, forms, quantity steppers, dynamic totals, generated deep links — use write_page_code. It stores separate HTML, CSS, and JavaScript fields and renders them as-is behind a strict Content-Security-Policy. Scripts, styles, forms, and event handlers all work. Use this as your default unless the page is purely static.
digy_write_page_code({
"site_slug": "my-portfolio",
"page_slug": "menu",
"title": "Menu",
"html": "<div id=\"app\"><button id=\"btn\">Add to cart</button></div>",
"css": "#app { font-family: sans-serif; } #btn { background: #10b981; color: white; }",
"javascript": "document.getElementById('btn').addEventListener('click', () => { alert('Added!'); });"
})
write_page_code is upsert (creates the page if missing, otherwise overwrites) and publishes immediately — the page is live in code mode the moment the tool returns. Use get_page to read back the source for idempotent re-edits.
Content-Security-Policy (code mode). Code-mode pages run under a strict CSP. Plan around these constraints:
| Directive | Allows | Blocks |
|---|---|---|
script-src |
inline JS (<script>, onclick, etc.), /js/page-sdk.js, and external scripts from cdn.jsdelivr.net, cdnjs.cloudflare.com, unpkg.com, esm.sh, code.jquery.com, cdn.tailwindcss.com |
all other external script sources |
style-src |
inline <style> and inline style=""; external CSS from any https:// URL |
non-https CSS |
img-src |
https:, data:, blob: (uploaded asset URLs work) |
non-https images |
connect-src |
same-origin only (the Page SDK API works) | external fetch/XHR to other domains |
form-action |
same-origin only | cross-origin form POSTs (use JS navigation instead) |
WhatsApp / external handoffs: opening a WhatsApp deep link is a navigation, not a fetch — it is allowed. Build the link in JavaScript and trigger it with
location.href = 'https://wa.me/…?text=…'or an<a href>click. Do notfetch()external APIs (blocked byconnect-src).
Size limits (per page):
| Field | Limit |
|---|---|
html |
1 MB |
css |
1 MB |
javascript |
5 MB |
head |
200 KB |
| Combined | 10 MB |
<head> markup rules. The optional head field may contain only title, meta, style, link, and script elements:
on*event-handler attributes are rejected.<meta http-equiv>and<meta name="referrer">are rejected (platform-managed).<link rel>must be one ofstylesheet,icon,preconnect,preload, with anhttps://href.- Head
<script>must have ansrcfrom an approved CDN (cdn.jsdelivr.net,cdnjs.cloudflare.com,unpkg.com,esm.sh,code.jquery.com,cdn.tailwindcss.com) and no inline content — put your custom code in thejavascriptfield, not inhead.
Caching & publishing. Published pages are cached server-side (HTTP
ETag/304+ a 24-hour file cache) to keep large game pages fast under load. Republish a page to see changes immediately — any publish or draft save rotates the cache key automatically. Previews are always fresh (Cache-Control: no-store). For HTML5 games, minify and bundle your JS before publishing so the initial render stays quick.
2b. Simple static pages — update_page (sanitized mode)
For content-only pages (text, images, links) where you are certain no JavaScript, class-based CSS, or forms are needed, update_page is lighter (a single content field). The HTML is sanitized on render: scripts, styles, forms, and event handlers are stripped for safety.
digy_update_page({
"site_slug": "my-portfolio",
"page_slug": "home",
"title": "Home",
"content": "<h1>Hello world</h1><p>Welcome to my site.</p>"
})
Sanitized mode keeps: all standard content tags (<p>, <h1>–<h6>, <a href="https://…">, <img src="https://…">, <ul>, <table>, <div>, <span>, etc.), and inline style="" attributes.
Sanitized mode strips (removed on render):
| Removed | Why |
|---|---|
<script> |
No JavaScript |
<style> |
No class-based CSS (inline style="" still works) |
<form>, <input>, <button>, <textarea>, <select>, <option> |
No forms |
<iframe>, <object>, <embed>, <applet> |
No embedded frames/plugins |
<link>, <meta>, <base> |
No document-level markup |
on* attributes (onclick, onload, …) |
No event handlers |
javascript:, data:, vbscript: URL schemes |
No script URLs |
If a page might ever need interactivity, start with
write_page_code(2a) to avoid rebuilding it later.
Reading & deleting pages
- Use
digy_get_pageto read a page back. The response includesrender_mode("sanitized"or"code"); code-mode pages also returnhtml,css,javascript, andheadso you can re-edit idempotently. - Use
digy_delete_pageto remove a page. - Use
digy_get_site/digy_list_sitesto inspect your sites.
3. Add data collections (optional)
Collections are like database tables for structured data (e.g. products, testimonials, form submissions).
digy_create_collection({
"site_slug": "my-portfolio",
"name": "Testimonials",
"slug": "testimonials",
"access_rules": { "read": "public", "write": "public" }
})
access_rules controls the public Page SDK API (see digy_get_sdk_reference):
| Key | public |
authenticated |
private |
|---|---|---|---|
read |
anonymous OK | logged-in visitors only | never via public API (owner only) |
write |
anyone can submit | logged-in visitors only | never via public API (owner only) |
Default when omitted: { "read": "public", "write": "private" }. Change rules later with digy_update_collection_rules.
Manage records as the owner with digy_create_record, digy_list_records, digy_update_record, and digy_delete_record. Record updates support a mode of "merge" (default — shallow-merge fields) or "replace" (overwrite the whole data object).
MCP vs browser SDK — record capabilities
Both layers can now read, create, update, and delete records. The browser SDK enforces the collection access_rules on every call; the MCP tools run as the owner, so they bypass access_rules and can mutate any collection.
| Operation | MCP tool (digy_*) |
Browser SDK (PageSDK.records.*) |
Access enforced by |
|---|---|---|---|
| List/read | list_records |
records.list |
SDK: read rule · MCP: owner (any) |
| Create | create_record |
records.create |
SDK: write rule · MCP: owner (any) |
| Update | update_record |
records.update |
SDK: write rule · MCP: owner (any) |
| Delete | delete_record |
records.delete |
SDK: write rule · MCP: owner (any) |
Public deletes via the SDK are hard deletes (no trash/undo). Restore and soft-delete are owner-only.
Reserved page slugs
These slugs are platform-reserved and cannot be used for pages (or sites) — they collide with platform routes:
api admin dashboard assets storage login register logout mcp
Pick descriptive alternatives (e.g. sign-in, media, control-panel, data-api).
4. (Optional) Enable visitor login
digy_configure_site_auth({ "site_slug": "my-portfolio", "enabled": true, "auth_methods": ["email"] })
Then create visitor accounts with digy_create_end_user (requires the end_users plan feature). Visitor login is handled by the browser SDK (PageSDK.auth.login) on the published site — it never exposes tokens to JavaScript.
5. Publish the site
digy_publish_site({ "site_slug": "my-portfolio" })
Publishing charges hosting credits (one month upfront, renewable every 30 days). The site becomes live at:
https://{subdomain}.{root_domain}/{site_slug}
https://{subdomain}.{root_domain}/{site_slug}/{page_slug}
- Insufficient credits? Publishing fails with the required/balance amounts — top up via the dashboard.
- Already live?
digy_publish_siteis a safe no-op. - Take it offline with
digy_unpublish_site(credits are not refunded).
Interactive patterns (code mode)
These require write_page_code — they are not possible with update_page (sanitized mode strips scripts, styles, and forms).
Images
Upload images with digy_upload_asset (returns a public url), then reference them in either mode:
<img src="https://{subdomain}.{root_domain}/storage/tenants/{tenant}/{site}/assets/photo.jpg" alt="Menu item">
Asset URLs survive sanitized mode (only javascript:/data: schemes are stripped — https:// is fine). In code mode all of https:, data:, and blob: are CSP-allowed for images.
To use an AI-generated image (e.g. from ChatGPT/DALL-E) or a stock photo, call digy_import_asset_from_url with the public HTTPS URL — the image is downloaded and copied permanently into your site storage. Import promptly after generation, since some AI image URLs expire. The returned url can then be referenced in <img src> exactly like an uploaded asset.
Cart / quantity steppers / live totals
Store cart state in a plain JS object, render it with class-based CSS, and update the DOM on every change. Example skeleton:
const cart = {}; // { "item-id": qty }
function addItem(id, name, price) {
cart[id] = (cart[id] || 0) + 1;
render();
}
function removeItem(id) {
if (cart[id]) { cart[id]--; if (cart[id] <= 0) delete cart[id]; }
render();
}
function total() {
return Object.entries(cart).reduce((sum, [id, qty]) => sum + priceOf(id) * qty, 0);
}
function render() {
// update DOM: cart count badge, line items, total
}
Tabs / show-hide sections
Use buttons that toggle a CSS class (hidden / active) on section containers — no navigation needed.
Modal checkout
A hidden <div> overlay shown via a class toggle; collect table number / name / phone in <input> fields (allowed in code mode), then build the order message.
Send order to WhatsApp
WhatsApp deep links open by navigation (CSP-allowed), not fetch. Build the text and redirect:
function sendToWhatsApp(phone, text) {
const url = 'https://wa.me/' + phone + '?text=' + encodeURIComponent(text);
window.location.href = url; // navigation — allowed under CSP
}
Persist orders (optional)
To also save the order server-side, create a collection with write: "public" (or authenticated) and submit via the Page SDK:
await PageSDK.records.create('orders', {
table: 5, name: 'Ali', items: cart, total: total()
});
This is a same-origin fetch — allowed under connect-src 'self'.
URL structure
{subdomain}.{root_domain}/{site} -> serves the "home" page (or first page)
{subdomain}.{root_domain}/{site}/{page} -> serves a specific page
/api/public/sites/{site}/... -> Page SDK JSON API (same origin)
{subdomain} is your workspace subdomain (set at registration). {root_domain} is the platform root domain.
Credits & publish billing
- Publishing debits
hosting_credits_per_month(default 5) credits and starts a 30-day hosting window (hosting_charged_until). - Renewal: a scheduled job re-charges on/after the due date. Insufficient balance suspends the site (
suspended_for_payment = true,published = false); a successful renewal republishes it. - Unpublishing takes the site offline immediately but does not refund credits and does not reset the hosting window.
- Track balance with
digy_get_credit_balance.
Plan limits
Limits and feature flags are configured by the platform admin and gated across both the dashboard and MCP:
pages— max pages per site (digy_update_page/createblocked past the limit).total_entries— combined cap on records + end users per site (digy_create_record/digy_create_end_userblocked past the limit).storage_mb— total asset bytes per site.- Features:
collections,end_users,media_library,analytics,csv_export— toggling a feature off blocks the related tools.
Use digy_get_site / digy_get_site_analytics to inspect a site, and digy_get_build_guide / digy_get_sdk_reference to retrieve these docs at runtime.