Resources

This page covers generic resource usage in Doors for src and href.

Use it for:

  • files on disk
  • embedded or virtual filesystem content
  • generated bytes or strings
  • handler-backed links
  • proxied URLs

Generic resources usually fall into two groups:

  • default resources, served through private, user-scoped URLs
  • cached resources, served from shared resource storage with hash-based URLs

For JavaScript resources, see JavaScript. For stylesheet resources, see Styles.

Start

If you already have a normal URL, use a normal attribute:

<img src="/assets/logo.png">
<img src="https://cdn.example.com/logo.png">
<a href="https://example.com/report.pdf">Open report</a>

Use a resource when the content belongs to your app and Doors should turn it into a browser URL:

<img src=(doors.ResourceLocalFS("./private/logo.png"))>

<a href=(doors.ResourceBytes(reportPDF)) name="report.pdf">
	Download report
</a>

In practice:

  • plain URL: write the URL directly
  • app-owned content: use a resource
  • managed resources are private by default

Syntax

You can attach a resource either as a direct attribute value or with modifier syntax.

Direct attribute value:

<img src=(doors.ResourceLocalFS("./private/chart.png"))>

<a href=(doors.ResourceBytes(reportPDF)) name="report.pdf">
	Download
</a>

Modifier syntax:

<img (doors.ResourceLocalFS("./private/chart.png"))>

<a
	(doors.ResourceBytes(reportPDF))
	name="report.pdf">
	Download
</a>

Modifier syntax assigns the value to src or href from the tag name.

Use modifier syntax with actual resource values such as doors.ResourceBytes(...) or doors.ResourceLocalFS(...). Raw []byte values and handler functions are supported on src= and href= values, but not as standalone modifiers.

Sources

File on disk

<img src=(doors.ResourceLocalFS("./private/chart.png"))>

Embedded or virtual filesystem

//go:embed assets
var assets embed.FS
<img src=(doors.ResourceFS(assets, "assets/chart.png"))>

Bytes already in memory

<a href=(doors.ResourceBytes(reportPDF)) name="report.pdf">
	Download report
</a>

String content already in memory

<iframe src=(doors.ResourceString(htmlSnippet)) name="preview.html"></iframe>

Simple custom response

<img src=(doors.ResourceHandler(func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "image/svg+xml")
	w.Write(chartSVG)
})) name="chart.svg">

Full hook with Door context

<a href=(doors.ResourceHook(func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool {
	w.Header().Set("Content-Type", "application/pdf")
	w.Write(reportPDF)
	return false
})) name="report.pdf">
	Download
</a>

If a hook returns true, the resource is served only once. That is useful for one-time downloads and other short-lived links.

To get a hook URL without an attribute, for an API, server-sent events, or a WebSocket, see Custom Endpoints.

Request body is not bounded by ServerRequestBodyLimit for resource hooks. Apply a safe limit yourself if you read the body in a ResourceHandler or ResourceHook callback.

Proxy

<iframe src=(doors.ResourceProxy("https://example.com/embed"))></iframe>

Plain URLs

For generic src and href, already-hosted URLs should be written as plain strings:

<img src="https://cdn.example.com/logo.png">
<a href="/downloads/report.pdf">Open report</a>

Shorthands

For plain src= and href= usage, resources support a few shorthand forms.

[]byte

[]byte is treated like doors.ResourceBytes(...):

<img src=(pngBytes)>

<a href=(pdfBytes) name="report.pdf">
	Download
</a>

Simple handler

A plain handler is treated like doors.ResourceHandler(...):

<img src=(func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "image/svg+xml")
	w.Write(chartSVG)
}) name="chart.svg">

Full hook

A full hook-style handler is treated like doors.ResourceHook(...):

<a href=(func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool {
	w.Header().Set("Content-Type", "application/pdf")
	w.Write(reportPDF)
	return true
}) name="report.pdf">
	Download once
</a>

Cache

Add cache when the resource should be cacheable on the client side instead of being downloaded again on each render:

<img
	src=(doors.ResourceFS(assets, "assets/logo.png"))
	cache>

<a
	href=(reportPDF)
	cache
	type="application/pdf">
	Open report
</a>

What cache changes:

  • the browser gets a hash-based URL it can reuse
  • the resource becomes publicly reachable by that URL
  • Doors keeps the cached entry in RAM

Use it for stable public content such as:

  • embedded files
  • local files that should behave like public assets
  • generated bytes or strings that are not user-specific

Do not use it for large files. Cached entries are stored in RAM.

cache only works with static resource inputs:

  • doors.ResourceFS(...)
  • doors.ResourceLocalFS(...)
  • doors.ResourceBytes(...)
  • doors.ResourceString(...)
  • raw []byte

Handler, hook, proxy, and plain string inputs cannot use cache.

Without cache, managed resources stay private to the current Door instance and can get a different URL on another render.

Even when name is set, a cached resource URL still contains a unique identifier. That makes cache a better fit for views and reusable assets than for user-facing download links where you want the cleanest possible filename.

URL in code

doors.NewResource(ctx, resource, contentType) returns the same cached URL without an attribute, for code that loads the file itself:

<script data:styles=({
	url, _ := doors.NewResource(ctx, doors.ResourceBytes(widgetCSS), "text/css")
	return url
})>
	const link = document.createElement("link")
	link.rel = "stylesheet"
	link.href = $data("styles")
	document.head.append(link)
</script>
  • set contentType for JavaScript and CSS; empty lets the server detect it, which gives text/plain for them
  • append a file name after a dot when needed: url + ".widget.css"
  • the error comes only from reading the resource, such as a missing file

Portable

Add portable when the URL must work without the browser session cookie:

<a
	href=(doors.ResourceHook(func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool {
		w.Header().Set("Content-Type", "application/pdf")
		w.Write(reportPDF)
		return false
	}))
	portable
	name="report.pdf"
	download>
	Download report
</a>

A private resource URL is tied to the browser session, so it fails when something other than the page fetches it: an Android WebView hands downloads to the system download manager, which sends no cookies; apps and in-app browsers open links outside the page's cookie jar. A portable URL carries its own access instead.

What portable changes:

  • the URL is a bearer link: anyone holding it can load the resource
  • the link works while the content that rendered it lives, and stops when that content is cleared, the page instance or session ends, or a one-shot hook returns true
  • a request through it needs no cookie and creates no session; the handler still sees the page's session, the stripped r.URL.Path, and Doors-Hook-Prefix as with any hook

portable works with private and nocache; on handler, hook, and proxy sources it needs no other attribute. portable with cache is an error.

Handler, hook, and proxy sources are always served through a hook, so private is accepted on them and changes nothing. They do not take cache or nocache.

Content Type

Use type when a managed generic resource should set the response content type:

<img
	src=(doors.ResourceFS(assets, "assets/chart.svg"))
	type="image/svg+xml">

That applies to managed generic src and href resources. It does not apply to handler-backed resources.

Notes

Use name when the generated URL should carry a readable file name:

<a
	href=(doors.ResourceBytes(reportPDF))
	name="report.pdf">
	Download report
</a>

This is useful for browser filename hints, inspection, and extension-based content handling.

Resources can be attached to the usual src and href elements. In practice, the most common ones are:

  • a
  • img
  • iframe
  • link
  • script
  • video
  • audio
  • source

For generic non-script, non-stylesheet usage:

  • use a plain URL when the URL already exists
  • use a resource when your Go code owns the content
  • prefer modifier syntax when the resource is the main point of the element
  • use cache only for stable public content
  • use portable for download links fetched outside the page, such as Android WebView downloads
  • use name when the generated URL should look like a real file

<script>, <style>, and <link rel="stylesheet"> have richer resource behavior than the generic flow described here.