> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aikeedo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Public assets

> Publish CSS, JavaScript, images and loader scripts from an Aikeedo plugin to the web root with the extra.public manifest entry.

Files inside `extra/extensions/` aren't web-accessible. To serve a stylesheet, script or image, list it in `extra.public`, and the installer copies it into the web root when the plugin is installed or updated.

## Declare files to publish

```json composer.json theme={null}
{
  "extra": {
    "public": [
      { "source": "public/assets/*", "target": "." },
      { "source": "widget/dist/loader.js", "target": "/acme-feedback.js" }
    ]
  }
}
```

By default, files land in `{web root}/e/{vendor}/{package}/`, which is served from `/e/{vendor}/{package}/...`.

## Entry formats

| Entry | Copied to | Served from |
| - | - | - |
| `"assets/app.css"` (plain string) | `public/e/acme/hello/assets/app.css` | `/e/acme/hello/assets/app.css` |
| `{ "source": "dist" }` | `public/e/acme/hello/dist` | `/e/acme/hello/dist/...` |
| `{ "source": "dist", "target": "." }` | `public/e/acme/hello/dist` | `/e/acme/hello/dist/...` |
| `{ "source": "dist", "target": "assets" }` | `public/e/acme/hello/assets` | `/e/acme/hello/assets/...` |
| `{ "source": "dist/*", "target": "." }` | The contents of `dist` into `public/e/acme/hello/` | `/e/acme/hello/...` |
| `{ "source": "widget/dist/loader.js", "target": "/loader.js" }` | `public/loader.js` | `/loader.js` |
| `{ "source": "dist", "target": "/." }` | `public/dist` | `/dist/...` |

A plain string keeps the whole source path, which is why the newer object form is clearer. A `target` that starts with `/` is resolved against the web root, so use it sparingly: that's a shared namespace, and a generic name like `/sdk.js` can collide with another plugin.

<Note>
  The object form requires a recent `heyaikeedo/composer`. Require `"^1.2.0"` when you use `{ "source": ..., "target": ... }` entries, and `"^1.0.0"` when plain strings are enough.
</Note>

## When files are copied

| Composer event | What happens |
| - | - |
| Package installed | Files are copied, and the copy is recorded in `vendor/aikeedo-file-mappings.json` |
| Package updated | Recorded files are removed, then copied again |
| Package uninstalled | Recorded files are removed, and the now-empty `e/{vendor}/{package}` directory is cleaned up |

The web root comes from the `PUBLIC_DIR` environment variable, so installations that serve from `public_html` work without any change on your side.

<Warning>
  Only files that existed when the package was installed are copied. Editing a file during development doesn't republish it: run `composer require acme/hello` again, or copy it yourself while iterating.
</Warning>

## Reference published files

```twig theme={null}
<link rel="stylesheet" href="/e/acme/hello/assets/app.css">
<script type="module" src="/e/acme/hello/assets/app.js"></script>
```

For a build with hashed filenames, publish the Vite manifest along with the bundle and resolve the name in PHP:

```php theme={null}
$manifest = json_decode(
    (string) file_get_contents(dirname(__DIR__) . '/public/assets/.vite/manifest.json'),
    true
);

$file = $manifest['assets/src/index.js']['file'] ?? null;
$url = '/e/acme/hello/' . $file;
```

<Tip>
  Hashed filenames from an older build aren't deleted when you publish a new one, because the mapping records what the current install copied. Clean stale files out of your build directory before packaging a release.
</Tip>

## Development workflow

Point an environment variable at your dev server, and fall back to the published path:

```twig theme={null}
{% set base = env.ACME_HELLO_ASSETS_SERVER|default('/e/acme/hello') %}
<script type="module" src="{{ base }}/assets/app.js"></script>
```

```ini .env theme={null}
ACME_HELLO_ASSETS_SERVER=http://localhost:5175
```

Remember that `env` exposes the whole environment to templates, so read only the key you need and never dump the object.

## What not to publish

* Source files, tests and configuration. Publish build output only.
* Anything containing credentials. Published files are world-readable.
* Large media that would be better served from the CDN. See [Files and storage](/development/plugins/files-and-storage).

## Related

* [Manifest reference](/development/plugins/manifest)
* [Frontend integration](/development/plugins/frontend-integration)
* [Packaging and distribution](/development/plugins/packaging-and-distribution)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.