netresearch / assetpicker-bundle
Symfony bundle for AssetPicker: the proxy route for CORS-restricted storages and a Twig function that renders the picker configuration
Package info
github.com/netresearch/assetpicker-bundle
Type:symfony-bundle
pkg:composer/netresearch/assetpicker-bundle
Requires
- php: ^8.4
- netresearch/assetpicker: ^2.0
- symfony/config: ^8.0.13
- symfony/dependency-injection: ^8.0.13
- symfony/framework-bundle: ^8.0.13
- symfony/http-client: ^8.0.13
- symfony/http-foundation: ^8.0.13
- symfony/http-kernel: ^8.0.13
- symfony/routing: ^8.0.13
- twig/twig: ^3.27
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^13.3
- symfony/browser-kit: ^8.0.13
- symfony/twig-bundle: ^8.0.13
Suggests
- symfony/twig-bundle: Registers the assetpicker_config() Twig function in your application
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-24 11:34:41 UTC
README
This Symfony bundle integrates AssetPicker into Symfony applications: it serves the AssetPicker proxy as a route and renders the picker configuration from your Symfony configuration with a Twig function.
Requirements: PHP 8.4 or later, Symfony 8, netresearch/assetpicker 2.x.
Installation
-
Install via Composer:
composer require netresearch/assetpicker-bundle
-
Enable the bundle in
config/bundles.php(Symfony Flex does this for you):return [ // ... Netresearch\AssetPickerBundle\AssetPickerBundle::class => ['all' => true], ];
-
Add the AssetPicker configuration in
config/packages/asset_picker.yaml. It is passed to the picker as is:asset_picker: storages: media: adapter: entermediadb url: "https://em.example.org/openinstitute" proxy: true repo: adapter: github username: "netresearch" repository: "assetpicker" pick: limit: 1
When several configuration files set
asset_picker(for example one per environment), they are merged: maps such asstoragesrecursively, while a list such aspick.typesor an empty value replaces the earlier value as a whole.types: [file]in a later file therefore leaves[file], not[file, dir]. -
(Optional) To use the proxy, import its route in
config/routes/asset_picker.yaml.assetpicker_config()then setsproxy.urlto the route automatically, unless you configureproxy.urlyourself:assetpicker_proxy: resource: "@AssetPickerBundle/config/routes.php"
The route is
/assetpicker?to=<url>. Use aprefixon the import to move it.
Usage
The picker JavaScript
AssetPicker 2 is a Vue 3 application that you build into your own frontend. It is not published on npm (the assetpicker package on npm is the old 1.3.4), so install it from the Git tag and build it with a bundler that compiles Vue single-file components, for example Vite with @vitejs/plugin-vue or Webpack Encore with enableVueLoader():
npm install github:netresearch/assetpicker#2.0.0 npm install --save-dev vite @vitejs/plugin-vue
A minimal entry point that opens the picker from a button and hands the picked asset to your code:
// assets/assetpicker.js import { createAssetPickerApp } from 'assetpicker'; const config = JSON.parse(document.getElementById('assetpicker-config').textContent); document.querySelectorAll('[data-assetpicker]').forEach((button) => { button.addEventListener('click', () => { const picker = createAssetPickerApp({ el: button.dataset.assetpicker, config, onFinish(result, cancelled) { picker.unmount(); if (!cancelled) { button.dispatchEvent(new CustomEvent('assetpicker:pick', { detail: result })); } }, }); }); });
See the AssetPicker README for createAssetPickerApp, the result format and custom adapters.
The configuration
The Twig function assetpicker_config() returns the asset_picker configuration as JSON, with proxy.url pointing at the proxy route when the route is imported. Render it into a JSON script element and read it from your entry point:
<script type="application/json" id="assetpicker-config">{{ assetpicker_config() }}</script> <button type="button" data-assetpicker="#assetpicker-mount">Pick an asset</button> <div id="assetpicker-mount"></div>
<, > and & in configuration values are escaped, so a value cannot end the script element.
The proxy
Storages that send no CORS headers, such as EnterMediaDB, need the proxy. The route forwards the request to the URL in its to parameter with the application's http_client service, wrapped as described below, and returns the upstream response. Redirects are not followed; their Location is rewritten to go through the route again. A request without to is answered with 400 Bad Request.
The route runs on your application's domain, so the browser sends your application's cookies and HTTP authentication along. The proxy does not forward them: Cookie and Authorization are removed from the forwarded request, and Set-Cookie from the upstream response. A storage that needs a session cookie or an Authorization header therefore cannot be used through the proxy.
Any visitor who can reach the route chooses the target. The proxy therefore refuses targets on private, loopback, link-local and other non-public addresses (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 including cloud metadata endpoints, ::1, fc00::/7, fe80::/10 and the rest of Symfony\Component\HttpFoundation\IpUtils::PRIVATE_SUBNETS), and hosts whose name does not resolve. It sends its requests through the service assetpicker.proxy.http_client, a Symfony\Component\HttpClient\NoPrivateNetworkHttpClient around your http_client: it resolves the host name itself, sends the request to the address it checked, and checks the address the connection actually used. A refused target is answered with 403 Forbidden and is not requested. Redirects are not followed by the proxy; the browser follows the rewritten Location through the route again, where the redirect target is checked like any other target. The http_client service itself is not changed, so the rest of your application can still reach internal hosts.
If a storage is on an internal host, redefine assetpicker.proxy.http_client in your config/services.yaml with that host's addresses as the allow list (symfony/http-client 8.1 or later); every other internal address stays refused:
services: assetpicker.proxy.http_client: class: Symfony\Component\HttpClient\NoPrivateNetworkHttpClient arguments: $client: '@http_client' $allowList: ['10.1.2.3']
The proxy still reaches every public host. Restrict access to the route with your firewall and access_control if that is not wanted.
Upgrading from 1.x
Version 2 targets AssetPicker 2, which replaced the script-tag picker with a Vue 3 application and no longer ships a built picker.js.
- PHP 8.4 and Symfony 8 are required.
- The
assetpicker_url()Twig function and theassets:installlistener that copiedpicker.jsintopublic/bundles/assetpicker/are removed. Build the picker as described above, and replacenew AssetPicker({{ assetpicker_config() }})andrel="assetpicker"buttons withcreateAssetPickerApp(). - The route resource moved from
@AssetPickerBundle/Resources/config/routing.ymlto@AssetPickerBundle/config/routes.php. Route name (assetpicker_proxy) and path (/assetpicker) are unchanged. - The proxy answers a request without
towith400instead of an uncaught exception (500). - AssetPicker 2 renamed configuration keys:
picker.*is nowpick.*;adaptersanddebugare removed. See the AssetPicker changelog.
Development
composer install vendor/bin/phpunit vendor/bin/phpstan analyse
License
MIT