STORE FILE ASSETS: the store's general files tree (listStoreFiles / readStoreFile / writeStoreFile) holds standalone assets — css/js files, images, documents — as public Storage objects. The admin's code file tree mirrors it as the assets/ branch (files, folders and bundles inline), so a focus path like assets/site/custom refers to a file these tools address; a bundle segment like assets/{dir}/{bundleId}/{entry} refers to a bundle entry (writeTemplateAsset with that bundleId).
WHEN TO CREATE A FILE vs INLINE: template CSS/JS belongs INSIDE the template (writeWebTemplate css/js — scoped, versioned, previewed). ELEMENT behavior JS/CSS belongs ON the element (elements/{id} js, loaded only where placed) or in the shared element runtime (setup/elements.js, one merged script on element-bearing pages) — readRule("elements") for the channel contract; NEVER park store behavior in standalone site/js/*.js files loaded via headTags. Create a standalone file only when the asset is genuinely shared or referenced by URL AND is not element/template code: a manifest/robots-style text file, a downloadable document, or when the user explicitly asks for a file. Never duplicate design-system rules into a file — the design stylesheet is injected into every page already.
LOADING A SHARED STYLESHEET / FONT / THIRD-PARTY TAG SITE-WIDE — setup/web.headTags: a web template is three strings (html/css/js) rendered into the page BODY; it cannot add anything to <head>. The ONE channel for a global <link rel="stylesheet">, favicon or analytics snippet is setup/web.headTags (a web-font stylesheet is added in the console — Design › Properties › Fonts — which writes its <link> into a generated <!-- drop:fonts --> … <!-- /drop:fonts --> block of headTags: never edit that block, and carry it over unchanged when you rewrite headTags; a hand-written font <link> already there keeps working) — a raw-HTML string injected verbatim into EVERY rendered page's <head>, LAST (after the design/shell/fragment/template styles), so a stylesheet linked here overrides template CSS in the cascade. Store BEHAVIOR scripts do NOT belong here: element and template JS load through their own channels (readRule("elements")), and stacking store-file <script src> tags in headTags loads every behavior on every page — the only store-authored <script> justified in headTags is the tiny INLINE first-paint bootstrap (see below). To load a shared asset on every page: readStoreDocument("setup/web"), APPEND your tag to the EXISTING headTags string (never drop what is already there), then write the FULL string back with updateStoreDocument("setup/web", {headTags}). headTags FORMAT RULES: (1) store-file links MUST be SITE-RELATIVE /assets/{storagePath} paths (storagePath starts with store/{sid}/… — it is the storage URL's path after the bucket host, so a writeStoreFile url or listTemplateAssets ${assetUrlPrefix}/… URL converts by replacing everything through the bucket host with /assets); NEVER write absolute storage.googleapis.com URLs into headTags — the render server serves /assets/** same-origin and previews resolve them too. (2) NO raw <style> blocks or inline CSS in headTags — shared CSS belongs in the design system, per-page CSS in the template, file CSS behind a <link>. (3) External third-party URLs (fonts.googleapis.com, analytics CDNs) stay absolute — only THIS store's storage objects use /assets/…. It is site-wide, not per-page — to load an asset on ONE page only, put the <link>/<script> in that template's html instead. Order tags so overrides come last (a re-tint stylesheet after the base theme). Bundle links keep their ?v={version} query so a later bump can be busted by editing the tag.
URL CONVENTIONS:
- General file: writeStoreFile returns
url(public storage URL) — use it verbatim in templates. Same dir+name overwrites in place, so the URL stays stable across edits; text objects carry a short cache (edits show within ~5 minutes without a URL change). When a change must be visible IMMEDIATELY (or the object will be re-read/imported right after the write), prefer a NEW content-addressed name (hero-v2.jpg) over overwriting — the storage edge caches objects for ~minutes and an overwrite serves stale bytes inside that window. - A snippet that arms reveal/entrance classes before first paint must be INLINE in headTags (
<script>…</script>), never<script src … defer>— a deferred/linked script arms the classes after first paint and silently kills every entrance animation. Keep it to the tiny class-arming bootstrap with a failsafe; the reveal RUNTIME itself belongs in the element channels (readRule("elements")). - Bundle entry: reference via
${assetUrlPrefix}/${path}from listTemplateAssets; append?v={version}(the bundle's version from setup/design.assets) when writing NEW references so a later bump can be busted by editing the URL. - IN CONTENT and template html, keep writing full storage URLs — sites served by drop-core-server rewrite them to
/assets/{storagePath}automatically at render time; do NOT hand-write/assets/...paths into document content. The ONE exception issetup/web.headTags, where links must be the site-relative/assets/{storagePath}form (see above).
EDITING: read the current content first (readStoreFile) and write the FULL new content — writes replace the whole object. Only text files are writable (css/js/html/json/svg/txt/md/xml); images go through generateImage/importImageFromUrl. Other binaries (video, PDF, fonts, archives — up to 1 GiB) are uploaded, never written: on the LOCAL (stdio) MCP server with uploadStoreFile — only a file the user asked you to publish, from inside the project folder (the result is PUBLIC) — otherwise the user uploads them in the console's Files view or through the SDK (Files.upload). Never base64 a binary into writeStoreFile. File writes notify subscribed render servers via the files hook, so cached copies refresh on their own.
LARGE FILES: an object above 16 MiB (a video, a big download) is not served through the render server — /assets/{storagePath} answers a redirect to the storage URL, which serves byte ranges (video seeking works). Link it like any file; for a video use <video src="{url}" preload="metadata"> rather than autoplaying a large file.