docs
/
AI skills

Create, clone or template a site

A new site from blank, from a site template or as a copy of another site — what each way really creates (name, platform host, starter pages, default site, emails), what a clone copies and what it leaves behind, the fixes every clone needs, and deleting a site with everything it leaves behind.

A site is one record (datatype: "site") plus everything that names it: its pages (data.site = the site's name), its host mappings (site_domain records in the platform's root org) and, on live, a route for each custom domain. The site record's fields are in Sites. Copying a single page is Copy a page as a template. Connecting a domain is Connect a custom domain. The site's title, description and icons are Site identity, SEO tags and link sharing. Languages and currencies are Site languages and currencies.

"Tested" below means checked on a local engine with skilltest-site-* sites, which were deleted afterwards. Everything else is read from the code, with the file named. Paths are appengine src/site/ unless they say otherwise.

Some of this area has bugs that change what you should do. Read What it can't do yet before you start. The two that matter most:

  • A clone copies only 50 pages, fails with 409 when the source has a navigation, and takes the "default site" flag away from the source (step 4).
  • No delete removes the site's pages. A new site with the same name gets the old pages back.

The ways a site is made

WayWho uses itCallWhat it does
Sign-upthe platform, when an org is createdcreateSite (org-management.service.ts 2562)The org's first site, named <first three letters of the org id>site (phazzer → phasite), with the sign-up's site template.
Create sitethe API onlyPOST /site/create-site (site.controller.ts 201–207); POST /site/create is the same with name for siteName (496–501, site.service.ts 1908–1925)Everything below: plan check, name check, platform host and mapping, template, starter pages, default site, two emails.
New Site in Sites & Dev Environmentsa person, at /site-management (websitemint site-management/modern-site-app.tsx 802–808, 1013–1085)POST /site/add with { siteName, title, description, domainName } (modern-site-app.tsx 192–213 → site.service.ts 1697–1803)A bare site: plan check, name check, platform mapping, one email. No starter pages, no template, no hostName, never made the default.
Add Site in System Operationsplatform staff (system-operations/modern-companies-management.tsx 487–502)POST /site/add with { siteName, siteOrgId, title, template }As New Site. The template is saved on the record and never applied.
Clonethe API only; Studio has no button (modern-site-app.tsx 474–477 only shows a notice)POST /site/clone-site (site.controller.ts 214–220 → site.service.ts 500–541)A copy of another site's record, pages and navigation. No plan check, no host, no mapping, no email.
Apply templatethe API onlyPOST /site/apply-template (site.controller.ts 642–648 → site.service.ts 543–627)Copies a template site's pages into an existing site.
Bare recordthe API onlyPUT /repository/create with datatype: "site" (repositories/repository.crud.service.ts 962–976)The record and nothing else. Name rules apply; no plan check. Tested.

What each one does, side by side:

create-siteaddclone-siterepository create
Plan's site limit (403 Free plan allows maximum 1 site(s)…)yes, firstyes, firstno (tested)no (tested)
Name must match ^[a-z][a-z0-9-]*$yes, 422yes, 422yes, after lower-casing (tested: Skilltest Site Bad → 422 Invalid site name skilltest site bad)yes, after lower-casing
Name already used in the org409409updates that site instead (step 4)422 Site name already exist
data.hostName<name>-<org>.<SITE_HOST_NAME> (site.appmint.app on live; 235, 1219–1221)not set. Saves data.sharedDomain = <name>-<org>.site.appmint.app instead (1722, 1735)the source's hostName (tested)whatever you send
Host mapping (site_domain) for the platform hostyes (263–274)yes (1743–1755)none (tested)none
domainName givensaved as data.domain, mapped, route deployedsamethe source's data.domain, unmapped (tested)—
Template appliedyes, before the starter pages (253)no—no
Starter pagesyes (256)nothe source's pagesno
Made the default sitewhen it is the org's only site (417)nocopies the source's flag (see step 4)if you send isDefault: true
Emails"Website Publish Notification" to the org's email and [email protected], plus the site-created notification (294–319). Not for a BusinessMade org (291).site-created (1777–1794)nonenone

The site's name and hosts

  • The name is the site's key. Pages belong to a site by its name (data.site), and the platform host is built from it. It is lower case, starts with a letter, and holds only a–z, 0–9 and - (site.service.ts 216–220). It can't be changed: Site Config shows it read-only ("Set when the site was created — not editable", websitemint site-config-form.tsx 229, 749). To "rename" a site, clone it to the new name, fix the clone (step 5), then delete the old one (step 6).
  • The platform host is <name>-<org>.site.appmint.app on live (<name>-<org>.localhost:3200 on a local engine). It answers only when a site_domain mapping for it exists. Read a site's mappings with GET /site/get-site-host-mappings/<site name>.
  • A route (Kubernetes ingress) is deployed for every mapped name that doesn't end in .site.appmint.app (registerSiteDomainName 889–898). On live that means custom domains only. On an engine whose SITE_HOST_NAME is something else (a local one), every new site's platform host gets a route deployed too.
  • homePage names the page / shows, by name, slug or id. It defaults to index. A site with no page by that name answers / with a 404. See Sites.
  • The default site (data.isDefault, Site Config › Default site, "Serves any request that matches no other site", site-config-form.tsx 802) answers any host that no mapping claims. Only one site in an org holds it: the field is exclusive in the site schema, so writing true on one site sets it to false on the others (repositories/repository.crud.service.ts 1316–1370).

A site name that doesn't exist finds the default site. getSite falls back to the org's default site when no site has the name asked for (site.service.ts 111–114). Tested: GET /site/get-site/skilltest-site-nosuch answered with phasite. The calls that change a site (create, add, clone, remove, apply-template, Repair Site Domain) look it up by its exact name instead, and answer 404 for a name no site has (findSiteByName). Read the answer's data.name before acting on it, and always send the exact name.

Starter pages

Only createSite (sign-up and /site/create-site) seeds pages (seedStarterPages, 341–433). It copies two pages from sites in the platform's shared org (SHARED_ORG):

Copied fromSaved asWired toPicked
site landing-pageslanding-pagedata.homePageone page at random
site account-pagesbase-layoutdata.myAccount.layoutone page at random, among pages that can frame others: isRoot, two or more screens, or no <main> (account-layout-starter.ts)
  • {{siteName}}, {{siteTitle}}, {{orgId}} and {{companyName}} in the copied pages are filled in on the way (361–366). companyName is the org's display name, else its id; a new org has only its id.
  • The org's first site is made the default (417).
  • It never throws. If the shared org has no such pages, the site gets none and homePage stays index, so / is a 404.
  • The template's own home page is overridden. The template is applied first, then homePage is set to the random landing-page. Point homePage at the template's home page afterwards if that's what the person wanted.

The starter My Account layout is named base-layout, but the feature pages fall back to a page named account-layout. A new site's /blog, /book and other feature pages draw with no header or footer until it has one. See Built-in pages and templates.

Site templates

A site template is a pointer kept in the shared org: a flexdata record with data.application: "site-template" and data.content: { siteSk, orgId, siteName, title, description, image, … } (makeSiteTemplate, 644–713). Applying one reads the source site live, so fixing the source fixes the template.

Which template a name finds (resolveSiteTemplate, 715–741):

  1. In the shared org, a template whose content.siteSk equals the name, or whose data.name is <org>-<site>. Its source site is read from its own org.
  2. Otherwise a site in the same org, by name or id. Tested: templateName: "skilltest-site-src" and the source site's id both worked.

What applying copies (copySiteContent, 565–600):

PartCopied
PagesThe first 50 (the most recently changed first), each whole, with its style and post, re-pointed at the target site. Same names and slugs as in the template.
Main and footer navigationyes, as new records, which fails when the org already has one by that name (see step 4)
data.theme, data.settingsmeant to, but not saved. Tested: the target site's record was unchanged (version 0) after the apply.
Everything else on the template's record (home page, features, My Account layout, languages, Storefront settings)no

Apply template always says it worked. Every error is caught and logged (site.service.ts 621–625), and the answer is "Template '<name>' successfully applied to site <site>". Tested: a template that doesn't exist got that answer, and so did applying the same template twice, which copied nothing the second time (a page name already on the site makes the first page's create fail and stops the copy). Count the target's pages before and after.

Registering a template doesn't work today. Studio's Make Site Template button (shown only in the shared org, websitemint welcome-screens/buttons/site-action-buttons.tsx 34–50, 176–184) calls POST /tools/make-site-template, which doesn't exist (tested: 404 Cannot POST). The engine's POST /site/make-site-template passes the whole body as the site name, so it never finds the site (tested: 404 Site not found: [object object] in org phazzer).

The template gallery shows page templates, not site templates. The setup wizard's "Let's build your website" step and the Page templates view list the pages[] inside each registered template (setup-wizard/create-website-step.tsx 230–250) and copy one page with create-page-from-template. A template registered by today's code has no pages[], so it adds nothing to the gallery. Copying a page template is covered in Copy a page as a template.

Ask first

  • New, copy, or template? A blank site, a copy of one of their sites (which one, by name), or their site with a template's pages added.
  • The new site's name (lower case, letters, digits and hyphens, starting with a letter). It becomes part of the platform address and can't be changed later. Also its title and one-line description.
  • Should it become the default site? Usually not: the default site answers every address nothing else claims.
  • A custom domain? Connect it afterwards, with Connect a custom domain. Don't pass domainName at creation.
  • For a copy: what must change in it (domain, title, which pages), and whether the source must stay the default site.
  • For a delete: the exact site name, and whether its pages, navigation and files should go too.

Steps

1. Sites & Dev Environments: what's there now

  • The org's sites: call_appengine GET /site/org-sites/<org>. Each has data.name, data.hostName, data.isDefault, data.homePage, data.domain and links (the addresses it answers on, site.service.ts 182–207).
  • The plan's room: GET /site/limits. The sites row has used, limit and state (full means no more sites). Tested on the local org: used 1, limit 1, state full.
  • For a copy, the full source record: repo_find_one on site with its id. Note isDefault, domain, hostName, mainNavigation, footerNavigation, features, myAccount, storefront.
  • For a copy, its pages: repo_find on page with { "data.site": "<source>" } and a page size of 200. Count them.

Hands on: the site list, the plan's room, and for a copy the source record and its page count. Check: Studio › Sites & Dev Environments (/site-management) shows the same sites.

Stop: if the plan is full, say so and stop. Adding a site needs a plan upgrade. Don't go around the limit with a clone or a bare record.

2. Sites & Dev Environments: create a blank site

Stop: creating a site emails the account holder (and, for create-site, Appmint support). Say so, and wait for yes.

Make it with call_appengine POST /site/create-site:

{ "siteName": "spring-shop", "title": "Spring Shop", "description": "Our spring pop-up shop.", "template": "<optional template name>" }

siteOrgId defaults to the calling org. Another org's is refused with 403 Cannot change a site in another organization (assertOwnOrg). Every site route here is staff only (ContentAdmin or ConfigAdmin).

Answers to expect:

AnswerMeans
200 { site, domainRecords, sharedDomain, message }made. sharedDomain is the platform host.
403 Free plan allows maximum N site(s)…the plan is full (checked before anything else)
422 Invalid site name…fix the name
409 Site <name> already exists in organization <org>a site has that name

A bare site record (for a copy by hand, step 4): make the record and give it its address:

  1. PUT /repository/create { "datatype": "site", "isNew": true, "data": { "name": "spring-shop", "title": "Spring Shop", "description": "…", "homePage": "index", "publishedPagesOnly": false } }. Don't send isDefault, domain or hostName.
  2. POST /site/fix-domains { "siteName": "spring-shop" } (Studio: Site Config › Address › Repair Site Domain). It maps <name>-<org>.site.appmint.app and writes data.hostName (site.service.ts 1942–2032). See Connect a custom domain.
  3. Give it pages: step 3, or Build a website page.

This path has no starter pages and sends no email. Not tested here: the local engine's hosts would deploy a route.

Studio's New Site (POST /site/add) makes a site with no pages, no hostName and no template. Use the API call above instead.

Hands on: the new site's name, its id and its platform host. Check: GET /site/get-site-host-mappings/<name> lists the platform host. GET /site/get-site/<name> answers with that name, not the default site's. Its pages: landing-page and base-layout (when the shared org has starters), plus the template's.

3. Build Studio: start from a template

To add a template site's pages to a site that exists:

POST /site/apply-template
{ "siteName": "spring-shop", "templateName": "<shared-org template name or siteSk, or a site of this org>" }

Count the site's pages before and after, because the answer is the same whether it worked or not. Pages whose names the site already has stop the copy. Then set homePage to the template's home page (repo_update on site, patch: { "homePage": "<page name>" }). Copy any theme or settings by hand: they aren't copied.

For a single page from the platform's page templates, use Copy a page as a template.

Stop: on a site that serves drafts (publishedPagesOnly not true), the new pages are live at once. Name the URLs and wait for yes.

Hands on: the site's page list. Check: repo_find on page with { "data.site": "<site>" } shows the template's pages. Open the home page on the site.

4. Sites & Dev Environments: clone a site

POST /site/clone-site:

{ "sourceOrgId": "<this org>", "sourceSiteName": "phasite", "destOrgId": "<this org>", "destSiteName": "phasite-redesign" }

What it does (site.service.ts 500–541, 565–600, 786–833):

PartWhat the clone getsTested
The site recordall of the source's data, with only name changed: hostName, domain, isDefault, features, myAccount, storefront, languages, currencies, tracking, homePage…yes: hostName and domain were the source's
Pagesthe first 50 pages of the source, each whole (screens, html, style, post), with data.site changed, same names and slugsyes: of 53 pages, the 3 oldest were left out; style and post came across
Page statedraft, whatever the source's was. A published copy isn't made.yes
Main and footer navigationnew records with the same name. Navigation names are unique in an org, so this fails with 409 A navigation with name "…" already exists, after the site record was made and before any page was copied.yes
theme, settingsyes (they are in data)yes
Host mappingsnone. The clone's own platform address is a 404, and it borrows the source's hostName.yes
Uploaded filesnot copied. Pages still point at the source's file URLs, which keep working while those files exist.code
Posts, products, forms, collections, CRM recordsnot copied. They belong to the org, not to a site.code

Into a site name that already exists, the clone replaces that site's data with the source's, renames each of its pages to old_<name> (first 50 only), then copies the source's pages in (site.service.ts 517–525, 786–796). Tested: the renamed page kept its slug, so it still answers at its old address.

Cloning the default site makes the clone the default. The copy carries isDefault: true, and that field is exclusive, so saving the clone sets it to false on the source (site schema isDefault.exclusive; repositories/repository.crud.service.ts 976, 1514). The source stops answering unmapped addresses. Read from the code; not tested, because the only default site on the test org was off limits.

So check before you clone, and pick the path:

The sourcePath
≤ 50 pages, no mainNavigation or footerNavigation, not the default sitePOST /site/clone-site, then step 5
any of those failcopy by hand: make the site as a bare record (step 2's bare record) with the source's data minus name, hostName, domain, isDefault, mainNavigation and footerNavigation, then copy each page as in Copy a page as a template with site set to the new name, then step 5

sourceOrgId and destOrgId must both be the calling org; another org's is refused with 403 Cannot change a site in another organization.

Hands on: the clone's name and id (repo_find on site by name; the clone's answer may not carry sk). Check: the clone's page count equals the source's. Read the source again: its isDefault, version and page count are unchanged.

5. Site Config: fix what a clone copied

Each of these is a write to the clone only, except the last:

  1. Its own address. Clear the borrowed domain, then repair: POST /repository/update-partial/site/<clone sk> { "data.domain": "" }, then POST /site/fix-domains { "siteName": "<clone>" }. Without the first, the clone's emails and links use the source's domain (tested: GET /site/base-url?siteName=<clone> answered the source's domain), and Repair tries to map the source's domain and reports already registered to another site.
  2. Not the default site. { "data.isDefault": false } on the clone. If cloning took the flag from the source, write { "data.isDefault": true } on the source, which also clears it on the clone.
  3. Its title, description and Site Features, as the person asked. features came from the source. Changing them is in Built-in pages and templates.
  4. Missing pages. Copy any page past the first 50 with Copy a page as a template.

Use dot-path keys only. { "data": { … } } replaces the whole record's data.

Stop: writing the source's isDefault changes which site answers unclaimed addresses. Say so and wait for yes.

Hands on: the clone's mappings and its corrected record. Check: GET /site/get-site-host-mappings/<clone> lists <clone>-<org>.site.appmint.app. GET /site/base-url?siteName=<clone> answers that host. Open it: the home page shows. Before the repair, a page can be previewed through any working host of the org at /__preview/page/<clone>/<page> (tested).

6. Sites & Dev Environments: delete a site

Stop: deleting is permanent for the site's addresses and sends the account holder a "site deleted" email. Name the site, its addresses and its page count, and wait for yes.

Studio: Sites & Dev Environments › the site's … › Delete Site ("This will permanently delete the site … and all its resources", modern-site-app.tsx 713–726, 1273–1300). It calls:

POST /site/remove
{ "siteName": "spring-shop", "deleteResources": true }

call_appengine asks for the person's confirmation first (a remove path), then send it again with "confirmed": true.

What it does (site.service.ts 1805–1906, 435–465): finds the site by its exact name (404 Site <name> not found in organization <org> otherwise), deletes the route of every mapped name, deletes the mappings, with deleteResources deletes the deployment, service, config map, secret and volume named after the site in a namespace named after the org, deletes the site record (to the trash), and sends site-deleted.

What no delete removes, so do it after (tested: the pages of a deleted site were all still there):

  • Its pages: repo_find on page with { "data.site": "<name>" }, then repo_delete each one. Include the old_ pages a clone made. Pages left behind come back on any later site given the same name.
  • Its navigation records, by the ids in mainNavigation and footerNavigation.
  • Its files, if they were only for this site.

DELETE /site/delete-site/:siteOrgId/:siteId doesn't work: the route reads siteOrgId into both values (site.controller.ts 230). Tested: 400 invalid id phazzer. Studio doesn't use it.

Hands on: nothing. Check: GET /site/org-sites/<org> no longer lists it, GET /site/get-site-host-mappings/<name> has no rows, and repo_find on page with its name finds nothing. If it was the default site, make another site the default (step 5).

When it's done

Tell the person: the new site's name, its platform address (and that a custom domain is a separate step), which pages it has and which is the home page, whether it's the default site, and what emails went out. For a clone, also say which of the source's settings it kept and which were fixed. For a delete, say what was removed and that the platform address no longer answers. Open Sites & Dev Environments and the new site.

What it can't do yet

  • Studio's New Site makes a site with no pages and no hostName. It calls /site/add, which seeds nothing and saves sharedDomain instead of hostName. A template sent to /site/add is saved and ignored.
  • No Clone button in Studio, and no apply-template screen.
  • Clone copies 50 pages at most, fails with 409 when the source has navigation (after making the site record), copies the source's hostName, domain and isDefault, and creates no mapping. It has no plan check.
  • Apply template doesn't copy theme or settings, copies 50 pages at most, stops at the first page name the site already has, and always reports success.
  • Site templates can't be registered: Studio calls a route that doesn't exist, and the engine route misreads its body. The page gallery reads a pages[] list that registration no longer writes.
  • No delete removes pages, navigation or files. /site/delete-site/… doesn't work at all.
  • The starter layout is base-layout, but features fall back to account-layout.
  • The site name can't be changed.

Don'ts

  • Don't call /site/remove or /site/apply-template before GET /site/get-site/<name> has answered with that exact name.
  • Don't clone a source that is the default site, has navigation, or has more than 50 pages. Copy it by hand.
  • Don't leave a clone with the source's domain or hostName.
  • Don't use a clone or a bare record to get around a full plan.
  • Don't pass domainName when creating a site; connect the domain afterwards.
  • Don't trust apply-template's answer. Count the pages.
  • Don't delete a site without deleting its pages.