# Theme assets and media (/themes/theme-assets)

A theme draws two kinds of pictures, and they live in different places:

| Kind             | Owner     | Where it lives                                    | How a theme names it                                                      |
| ---------------- | --------- | ------------------------------------------------- | ------------------------------------------------------------------------- |
| **Theme assets** | The theme | The theme's `assets/` folder, versioned with code | `asset('images/hero.jpg')` in code, `asset://images/hero.jpg` in settings |
| **Store files**  | The store | The store's media library (**Media library** in the admin) | `media://<id>` in settings a merchant fills; never in code       |

Assets travel with the theme: they are copied when the theme is duplicated, and installed with
it. Store files belong to the store: a merchant uploads them once and uses them in every theme,
product, email and page. Ship drawn pictures, patterns, icons and fonts as assets, and leave
photos to the merchant. The shipped themes carry no photos.

## The `assets/` folder [#the-assets-folder]

Every binary file of a theme sits under `assets/`, in folders of your choice
(`assets/images/hero.svg`, `assets/fonts/display.woff2`). A binary file anywhere else is refused,
by the code editor, the CLI and theme install alike.

| Type   | Extensions                                  | Cap                                                    |
| ------ | ------------------------------------------- | ------------------------------------------------------ |
| Image  | `jpg`, `jpeg`, `png`, `webp`, `avif`, `gif` | 5 MB a file; over 2 MB still installs, with a warning  |
| Vector | `svg`                                       | 256 KB; sanitized on the way in                        |
| Font   | `woff2`                                     | 1 MB; other font formats are refused, convert to WOFF2 |
| Icon   | `ico`                                       | 5 MB; over 2 MB still installs, with a warning         |
| Theme  | everything under `assets/`                  | 50 MB in total                                         |

Sizes are binary: 1 MB is 1024 × 1024 bytes.

- A path is relative to `assets/`, at most 256 characters, with letters and digits of any script
  plus `.`, `_` and `-` in each folder name; no leading dot, no spaces, and the file needs an
  extension.
- The bytes are checked against the type: an image must decode as the type its extension names,
  a font and an icon must carry their file signature, and an SVG goes through the same sanitizer
  as the store's library (scripts, event handlers, external links and embedded rasters refuse the
  file).
- Each asset is stored once per store, by its content, so two themes with the same file share
  it and a duplicated theme copies only the reference.

### Example assets [#example-assets]

Every shipped theme carries one or two small examples under `assets/images/`. Each one is used,
so `media/unused-asset` stays quiet, and together they show the patterns:

- `backdrop.svg`, a quiet still life in the theme's palette. It is the `asset://images/backdrop.svg`
  default of an image setting (the promo card of the default theme, the cover plate of Editorial,
  the feature image of Launch) and the value the home page starts with.
- `hatch.svg`, a hairline tile that `url(asset:images/hatch.svg)` lays under an empty image plate
  in the default, Editorial and Essential themes.

The files are drawn from plain SVG shapes, so there is no third-party artwork to license. A
theme whose code you changed before these files shipped did not receive them. Delete them if you
do not want them.

## In code: `asset()` [#in-code-asset]

```tsx
import { MediaImage, asset } from '@poltura/theme-sdk/media';

export function Hero() {
  return <MediaImage image={asset('images/hero.svg')} role='hero' alt='' />;
}
```

| Function                   | Returns                                                                                                                                     |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `asset(path)`              | The reference `asset://<path>`, for any prop that takes a picture (`MediaImage`). Throws on a malformed path                                |
| `useAssetUrl(path, role?)` | A URL: the role's rendition of an image, or the whole file without a role (always for SVG, fonts and icons). Null when the asset is missing |
| `useThemeAsset(path)`      | The resolved file (`url`, `width`, `height`, `hasAlpha`) or null. An asset has no alt text of its own                                       |

Roles are the same as for library files: `thumb`, `card`, `tile`, `pdp`, `zoom`, `hero`, `og`,
`logo` and `icon`. `MediaImage` draws an asset the way it draws a library file: a `<picture>`
with AVIF and WebP where the role allows, `srcset` for high-density screens, and the stored width
and height so the layout does not shift.

The theme build writes an **asset manifest** into every built version of the theme: each
asset's path, hash, type, size, pixel size and URLs. A path missing from the manifest renders a
placeholder, never a broken image, and the check reports it (below). Write the path as a literal
where you can. An `asset()` call with a computed path is allowed, but it turns off the
unused-asset report for the whole theme.

`media://` never appears in code. A store's file is something a merchant chooses, so it comes in
through a setting.

## In settings and pages: `asset://` [#in-settings-and-pages-asset]

Image settings, style backgrounds and every picture value of a page or layout JSON take a
reference: `media://<id>` for a store file the merchant picked, `asset://<path>` for a theme asset.

```ts
import { field } from '@poltura/theme-sdk/elements';

const settings = {
  background: field.image({ label: 'Background', default: 'asset://patterns/dots.svg' }),
};
```

An `image` setting may default to an asset, and only to an asset. A `media://` reference or an
address as a default throws, because a theme cannot know a store's files and must not depend on
another host.
`video` settings take no default. In the Customizer a merchant replaces the default with a file of
their library; the reference they pick is what the setting stores.

## In CSS: `url(asset:…)` [#in-css-urlasset]

```css title="src/styles/theme.css"
.hero {
  background-image: url(asset:patterns/dots.svg);
}
```

The theme build rewrites `url(asset:<path>)` (quotes optional, `asset://` accepted too) to the
file's address on the store's `/media/` path. A path that is not in `assets/` fails the build with
the error code `THEME_ASSET_MISSING`. Write `url(asset:…)` without the slashes, because that is
the form the check reads.

## The theme check: `media/*` rules [#the-theme-check-media-rules]

The CLI (`poltura theme check`), theme install and the code editor run the same rules. The
severity differs only where one of them knows less or where more is at stake:

| Rule                      | CLI / install / code editor                 | Reports                                                                                                                                       |
| ------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `media/no-remote-url`     | warning / error / warning                   | An `http(s)` address in a picture value. A shipped theme cannot depend on another host                                                        |
| `media/unsafe-url`        | error everywhere                            | `javascript:`, `data:` and similar in a picture or link value. An inline SVG data URL of up to 2048 characters is allowed in an `<img>` `src` |
| `media/unresolvable-path` | error everywhere                            | A bare or rooted path nothing serves (`/images/x.jpg`, `hero.jpg`) or a malformed `media://` / `asset://`                                     |
| `media/unknown-asset`     | error everywhere                            | An `asset://` or `asset('…')` path with no file in `assets/`; suggests the nearest name                                                       |
| `media/unknown-media`     | warning (install and code editor)           | A `media://` id that is not in this store's library                                                                                           |
| `media/ref-in-code`       | error everywhere                            | `media://` inside `src/`                                                                                                                      |
| `media/image-alt`         | warning everywhere                          | An `<img>` of the theme's own without `alt` or `decorative`                                                                                   |
| `media/asset-size`        | warning over the budget, error over the cap | A file over its type's budget or cap, the 50 MB total, or a font that is not WOFF2                                                            |
| `media/unused-asset`      | info everywhere                             | An asset nothing names                                                                                                                        |

The check's rule against placeholder addresses in text skips picture and link values. The
`media/*` rules judge an address there instead.

## In the code editor [#in-the-code-editor]

- **The folder is always there.** `assets/`, `assets/images/` and `assets/fonts/` show in the
  Explorer for every theme, including one with no asset yet, and stay when their last file is
  deleted or moved: they are folders of the workspace, not placeholder files, and nothing about them
  is stored with the theme.
- **Assets are files.** Drop images, SVG, fonts and icons from your computer into `assets/` in the
  Explorer. Renaming or moving a file updates every reference to it and saves the files it
  changed. A file the folder cannot hold (bytes outside `assets/`, over its cap, of another type) is
  refused with a notification that says why.
- **Previews.** An image, font or icon under `assets/` opens in a preview with its size, bytes and a
  **Used in** list. A font shows a specimen with sample text. **Preview asset** in the Explorer's
  context menu opens one, and an SVG file has a preview button in its editor title bar.
- **Media library.** The **Media library** view (its own activity-bar icon) lists the
  store's files by kind, with search. Drag a file into a JSON file to insert its `media://`
  reference, or run **Pick from the media library** there. **Upload to library** adds a file to the
  store's library. Upload videos on the admin's **Media library** page.
- **Completion and hints.** Typing `asset('`, `useAssetUrl('`, `useThemeAsset('`, `"asset://` or
  `url(asset:` lists the theme's assets, and `"media://` lists the store's images. Hovering a
  reference shows the file, and its link opens the preview. Inlay hints show the pixel size, the library file's name
  and alt text, or that a reference points at nothing.
- **Problems and quick fixes.** After every save the full theme check runs and fills **Problems**
  (source `poltura-check`), with fixes where the rule has one: **Use asset://…** for a path a file
  in `assets/` matches, **Did you mean asset://…?**, **Pick a file from the media library**,
  **Upload to the media library** for a remote address, **Add an image_alt setting** and **Remove
  this value**.

## Next [#next]

- [Share images (Open Graph)](/themes/og-images): templates for the image a shared link shows.
- [media reference](/themes/sdk/reference/media): `asset`, `MediaImage`, `useAssetUrl` and
  `useThemeAsset`.
