Skip to content

App sites

An app site serves one or more of your published apps standalone — on their own URL, outside the main Hutly workspace. Every site is reachable at <slug>.apps.hutly.com, and you can also connect your own custom domain (e.g. app.yourcompany.com).

You manage app sites under Organization → App sites (owners/admins), from the intellia app-sites CLI, or by asking Sammy (the managing-app-sites skill).

Pricing

App sites are billed monthly from your org’s credit balance:

  • $0.50 / month per slug site (every slug site).
  • $5.00 / month per active custom domain (charged only while the domain is live; a domain that’s still verifying or has been removed isn’t billed).

Fees are charged once per calendar month per site/domain and appear on your usage like any other credit consumption. As with all credit usage, if your balance runs low, auto-top-up (if enabled) covers it.

What’s on a site

  • A slug — the label in <slug>.apps.hutly.com. Lowercase letters, numbers, and hyphens, 2–63 characters, globally unique. It can’t be changed after creation (create a new site to change the URL).
  • Apps at paths — mount several apps on one site at different paths (/, /admin, …). Each app points at an approved version of a published app artifact. /sso, and anything under it, is reserved for the site’s own sign-in landing and can’t be used.
  • Site navigation — when on, apps that opt in (Show in nav) appear in a shared nav bar linking between them. Turn it off to serve each app standalone. With or without the nav, one app can link to another — see Linking between apps.
  • Custom domains — serve the site on your own domain in addition to the slug.

Public vs. private apps

An app served on a site is public — reachable without signing in — only if its approved manifest is marked public: true. A manifest can only be marked public when its declared scope is empty (no workflows, tables, or agents), since an anonymous visitor can’t be granted access to your org’s data — so an empty scope is required, but you must still set the flag. The server also refuses files, addressLookup and members on a public manifest, rejecting it with app_manifest_public_with_scope. Any app that isn’t marked public requires the visitor to be signed in to your organization; a private app’s path returns not found to anonymous visitors (so its existence isn’t disclosed).

Adding an app

You can only add an app at a version whose manifest has been approved (the same approval gate as pinning an app in the workspace). If the app isn’t approved yet, approve it first — see building apps. Then, in App sites → your site → Apps, choose the app, set its path and title, and optionally show it in the nav.

A path has to be free: another app already on it is rejected, and so is /sso or anything under it, which the site reserves for its own sign-in landing.

Linking between apps

An app on a site can send a visitor to another app on the same site, and the site swaps it in without a full page load. The app author writes an ordinary link — no platform-specific code:

<a href="/industry-awards">Industry awards</a>

A link starting / is read against the site root, so it lands the same way on the <slug>.apps.hutly.com address and on your custom domain. A link with no leading / is read against the app’s own mount — awards from an app mounted at /events means /events/awards.

An app can reach only another app on the site. The site checks the target against its own list of mounted apps and refuses anything else, so an app can’t send a visitor to the sign-in landing or to any other page the platform serves. Sending a visitor to another website is a separate permission an app declares in its manifest. A sign-in app on a site opens the websites it allowlists; a public page opens none. See building apps.

An app mounted at / is matched on / alone. If you’re serving an app that has its own internal pages, mount it at a real path (/events) and keep / for a single-view landing page.

What an app knows about its site

An app served from a site can read the site’s pages from its context:

const { site } = await window.hutly.ready;
const hasStock = site?.apps.some((app) => app.path === "/stock") ?? false;

site.apps lists { path, title, showInNav } for every page the signed-in person can open, in the site’s order. A public app sees only the site’s public pages, even when the person viewing it is signed in. It is absent when the app runs anywhere other than a site, such as the app preview in your workspace or the phone app. A module that adds its pages to a dependency’s site shows up here by its paths, so a home page can show a section’s shortcuts only when that section is installed.

An app can also count rows in, or aggregate, a table that another app on the same site declares, without declaring it itself:

const { count } = await window.hutly.tables.count("intellia_erp_sales_order", {
  filters: "status:eq:confirmed:and",
});

tables.aggregate follows the same rule. The count or the totals are all that come back; no row reaches the app. For an app user (a site sign-up), the app that declares the table has to be one their group can open.

Connecting a custom domain

Custom domains use a two-record DNS handshake:

  1. Add the domain (App sites → your site → Custom domains → Add domain). Hutly returns a first DNS record — a CNAME proving you own the domain.
  2. Create that record at your DNS provider, then click Verify now (Hutly also checks periodically).
  3. Once ownership is verified and the certificate is issued, a second record appears — the one that points your domain at Hutly.
  4. Create the routing record, verify again, and the domain goes Active.

Each record is shown as Type · Host/Name · Value · TTL with copy buttons and a live status (Pending → Verified).

DNS notes

  • Subdomains (app.yourcompany.com) use a CNAME for both records — the simplest path.
  • Root/apex domains (yourcompany.com) need an ALIAS/ANAME record for the routing step (a plain CNAME can’t live at a root domain). This is supported by Route 53, Cloudflare, DNSimple, and most managed DNS providers; if yours only supports CNAME records, use a subdomain instead.
  • Cloudflare: set the record’s proxy status to DNS only (grey cloud). A proxied (orange-cloud) record breaks certificate validation and routing.
  • DNS changes can take minutes to propagate. If verification stalls, double-check the record’s Name and Value match exactly.

Bundled into a module

An app site can also be named as a root in a module — a registry entry that packages it (and/or a workflow) into a single catalogue product.

Pages from a module’s dependencies

A module that depends on another can add its pages to that module’s site instead of shipping a site of its own. The ERP works this way: erp-base owns the one ERP site, and Stock, Manufacturing and Sales each add their sections to it.

In the dependent module’s tree, the app-site file names the site it joins instead of a slug, name and nav setting:

attachTo:
  module: erp-base   # must be in this module's dependsOn
  site: erp          # that module's app-site slug
apps:
  - path: /stock
    title: Stock
    showInNav: true
    sortOrder: 20
    manifest: !ref ../apps/stock/manifest.yaml
    artifact: !ref ../apps/stock/stock.html
  • A path on a site belongs to one module. Registering a page another module already ships is refused with module_app_path_claimed.
  • A module that owns a site lists the pages it ships in appPaths; an empty list is allowed for an owned site that ships no pages. An owner registered before appPaths existed is read as shipping every page on its site, and nothing can attach to it until it is deployed again: the attach is refused with module_attach_owner_paths_unrecorded.
  • A site has one owner. A second module that registers the same site as its own is refused with module_app_site_owned. Once other modules share a site, its owner has to keep listing its paths: registering it without appPaths is refused with module_attach_owner_paths_required. Deploy the owner of a shared site with CLI 1.10.0 or newer, which always sends its paths.
  • On activation, the pages land on the site the consumer already installed for the owner module, so the consumer names no slug for the dependent. Activating the dependent before the owner is refused.
  • A dependent’s pages come after the owner’s, and after any other module’s pages already on the site. sortOrder in the dependent’s tree orders only its own pages among themselves.
  • A page can call a workflow the owner module installs. List it in the manifest as a !ref into the owner’s tree; deploy and activation bind the page to the owner’s copy instead of making a second one.
  • Re-deploying the owner removes only the pages the owner registered. Pages a dependent attached stay.

From the CLI

intellia app-sites list
intellia app-sites create --slug acme --name "Acme Portal"
intellia app-sites apps add <siteId> --path / --artifact-id <id> --version-id <id> --title "Home"
intellia app-sites apps reorder <siteId> <appId1> <appId2>
intellia app-sites domains add <siteId> app.acme.com     # returns the DNS records to create
intellia app-sites domains verify <siteId> <domainId>    # re-checks; safe to repeat

See the CLI reference for the full command set.