Skip to main content

Configuration

Every option is documented inline in config/visual-feedback.php. The blocks:

KeyWhat it governs
enabledmaster switch
categoriesthe category list offered to the reporter. The first one is the default — the picker shows it and an untouched report carries it, so order the list to choose that default
fieldswhich fields are shown (subject, phone, …)
guestswhat a guest is required to give. Name and email are always offered to an unauthenticated reporter; these two switches make them mandatory. There is no key here that turns guest reporting off — a widget on a public page accepts a guest report, and refusing one is your page's decision about where the widget goes
attachmentsdisk, directory, counts, size caps, accepted types, image bomb guards
screenshotthe capture cascade, scale, caps, redaction attribute
metadatawhich browser metadata is collected
abusethe abuse driver, rate limits, the time trap and the challenge view
privacythe privacy notice guests acknowledge
channelswhich delivery channels run, and their queue tuning
mail, webhook, databaseper-channel settings
retentionhow long reports and orphaned files are kept — only once you schedule the two commands yourself, see Privacy and retention
uitrigger surface, floating button position, asset base URL
context_providersyour own classes that attach application context to a report — they implement ReportContextProvider, and authorizing what they expose is your duty, see Delivery channels

Environment variables

Everything below has a working default, so an install needs none of them. They exist for the settings that differ per environment — where reports go, which disk holds attachments, whether the capture asks for permission.

VariableDefaultEffect
VISUAL_FEEDBACK_ENABLEDtruemaster switch — see Turning it off
VISUAL_FEEDBACK_UI_VARIANTautowhich view tree renders: auto (WireKit when it is installed, plain otherwise), plain or wirekit
VISUAL_FEEDBACK_UI_TRIGGERfabfab, inline or none (you place the trigger)
VISUAL_FEEDBACK_UI_POSITIONbottom-rightwhich corner the floating button sits in
VISUAL_FEEDBACK_UI_ASSETSpublished pathbase URL of the capture bundle, if you serve it yourself — see the note below
VISUAL_FEEDBACK_FIELD_SUBJECTtrueshow the subject field
VISUAL_FEEDBACK_FIELD_PHONEfalseshow the phone field
VISUAL_FEEDBACK_GUEST_REQUIRE_NAMEfalsea guest must give a name
VISUAL_FEEDBACK_GUEST_REQUIRE_EMAILfalsea guest must give an email
VISUAL_FEEDBACK_ATTACHMENTS_DISKlocalthe disk attachments are stored on — keep it private
VISUAL_FEEDBACK_ATTACHMENTS_DIRvisual-feedbackdirectory on that disk
VISUAL_FEEDBACK_SCREENSHOT_STRATEGYautoauto, native, dom or off — see the capture cascade
VISUAL_FEEDBACK_SCREENSHOT_SCALE2render scale, or device for the device pixel ratio
VISUAL_FEEDBACK_SCREENSHOT_DARK_FALLBACK#111827background for a page that declares none and is dark
VISUAL_FEEDBACK_SCREENSHOT_DEBUGfalsethe renderer's own logging — off in production
VISUAL_FEEDBACK_ABUSE_DRIVERbuiltinwhich abuse gate runs
VISUAL_FEEDBACK_PRIVACY_SOURCEnoneurl, legal-consent, or a class name
VISUAL_FEEDBACK_PRIVACY_URLnoneyour policy page — required for url and for legal-consent; a class-name source supplies its own
VISUAL_FEEDBACK_PRIVACY_DOCUMENT_KEYprivacywhich legal-consent document the wording is read from
VISUAL_FEEDBACK_RETENTION_DAYSnonehow long a stored report is kept before visual-feedback:prune removes it. The package schedules nothing, so this deletes nothing until you schedule that command — see Privacy and retention
VISUAL_FEEDBACK_CHANNEL_MAILtrueenable the mail channel
VISUAL_FEEDBACK_CHANNEL_DATABASEfalseenable the database channel
VISUAL_FEEDBACK_CHANNEL_WEBHOOKfalseenable the webhook channel
VISUAL_FEEDBACK_MAIL_TOnonewhere reports are mailed — without it the mail channel stays idle
VISUAL_FEEDBACK_MAIL_FROM_ADDRESSapp defaultsender address
VISUAL_FEEDBACK_MAIL_FROM_NAMEapp defaultsender name
VISUAL_FEEDBACK_MAIL_LOCALEapp localea fixed locale, or reporter for theirs
VISUAL_FEEDBACK_MAIL_CONNECTIONdefault connectionqueue connection for the mail job — the worker that carries it, not a database connection
VISUAL_FEEDBACK_MAIL_QUEUEdefault queuequeue for the mail job
VISUAL_FEEDBACK_DATABASE_CONNECTIONdefault connectionqueue connection for the database job
VISUAL_FEEDBACK_DATABASE_QUEUEdefault queuequeue for the database job
VISUAL_FEEDBACK_WEBHOOK_URLnoneendpoint for the webhook channel
VISUAL_FEEDBACK_WEBHOOK_SECRETnoneHMAC key the payload is signed with — required, or the channel is skipped
VISUAL_FEEDBACK_WEBHOOK_INCLUDE_REPORTERtrueinclude reporter details in the payload
VISUAL_FEEDBACK_WEBHOOK_CONNECTIONdefault connectionqueue connection for the webhook job
VISUAL_FEEDBACK_WEBHOOK_QUEUEdefault queuequeue for the webhook job

Settings that live only in the config file

Most of what you will change has an environment variable. A handful does not, either because it is a value rather than a switch or because it is fine-tuning nobody reaches for twice. They are listed here so that "not in the table above" never has to mean "does not exist".

SettingDefaultWhat it does
screenshot.dark_fallback_color#111827the color the DOM stage paints behind a page whose background is transparent while color-scheme is dark. Any background your page actually sets wins through the normal cascade; this is only the last resort
screenshot.flatten_custom_elementstrueflatten custom elements before capture
screenshot.iframe_placeholdertruedraw a same-size placeholder naming the host for a cross-origin iframe
metadata.max_value_length2000the cap the metadata sanitizer applies to one collected value
metadata.user_agent_max512the cap for the user-agent string specifically, which is the one that gets long

Both screenshot booleans are real switches and do exactly what the table says: the configuration is handed to the capture bundle, which skips the corresponding pass whenever one of them is false. Open <dialog> elements are hidden unconditionally and need no key.

An <img src="*.svg"> is not fetched and inlined. It goes through the renderer's ordinary image path, so a cross-origin SVG served without CORS headers is missing from the capture like any other cross-origin image, and one served from your own origin is captured like any other image. That is worth stating because an inlining pass is the thing people assume, and assuming it hides a CORS problem behind a file format.

The attachments caps and the mail and webhook switches also have no environment variable, and they are documented where the decision gets made rather than here — see Integration contract and Delivery channels.

Serving the bundle yourself

VISUAL_FEEDBACK_UI_ASSETS takes a base URL, without a query string. The rendered <script> tag appends a cache-busting ?id= token of its own, derived from the installed package version, so the URL in your page source is not byte-identical to the one you configured. That is expected and nothing to correct — a Content-Security-Policy script-src source expression matches on the path and ignores the query, so nothing needs changing there either.

The token solves one of the two ways a bundle goes wrong, and it is worth knowing which. It handles republished but cached: after vendor:publish --force replaces the bytes on disk, the URL changes, so a browser or CDN holding the old file under a long max-age fetches the new one.

It does not handle forgot to republish. If the copy under public/vendor/visual-feedback/ is older than the one in vendor/, the token busts the cache and loads the same stale file again. Republishing after every upgrade of this package is still the only thing that prevents it — see Installation.