Appearance & Custom Chatbot Avatar
Merchants configure the storefront chat widget on Appearance (/app/appearance). This doc covers preset avatars, custom image upload (optimized for the storefront), and how settings reach the theme extension.
Related: Admin settings & chat widget · Recent changes · Storefront product cards
Appearance screen
Section titled “Appearance screen”| Route | File | Purpose |
|---|---|---|
/app/appearance | app/routes/app.appearance.jsx | Widget title, welcome message, theme color, launcher style, bubble position, visibility, launcher animation, icon inset, reply delay |
/app/appearance/avatar | app/routes/app.appearance.avatar.jsx | Upload or remove custom avatar (multipart POST) |
Settings are saved with the Polaris save bar. Custom avatar upload is a separate action (does not require clicking Save on the main form).
Avatar options
Section titled “Avatar options”| Preset | Behavior |
|---|---|
none | No avatar in the chat header |
1 … 5 | Built-in PNGs at /chat-avatars/{1-5}.png |
custom | Merchant-uploaded image (see below) |
Normalization: app/lib/widget-appearance.js → normalizeChatbotAvatarPreset().
Chat launcher style
Section titled “Chat launcher style”Merchants pick the floating chat button under Theme color on Appearance.
| Style ID | Storefront |
|---|---|
text (default) | Oval pill button with widget title text and theme color background |
1 … 15 | Preset PNG from /chat-buttons/{id}.png |
Normalization: normalizeWidgetLauncherStyle(). Image URL: buildChatButtonImageUrl(style, baseUrl).
Picker display order
Section titled “Picker display order”Admin thumbnail order is controlled by LAUNCHER_BUTTON_DISPLAY_ORDER in app/lib/widget-appearance.js. Rearrange IDs in that array to change picker order only; saved shop values and storefront URLs still use the image ID (e.g. "5" → chat-buttons/5.png).
GET /api/widget-settings returns:
widgetLauncherStyle—textor1–15launcherButtonUrl— absolute URL when an image style is selected;nullfortext
Storefront widget
Section titled “Storefront widget”chat-widget.js renders:
- text:
#appifire-ai-chat-bubble--textwithTITLEand theme color - image:
#appifire-ai-chat-bubble--imagewith<img src="launcherButtonUrl">
Launcher animation (widgetBubbleAnimation) applies to both modes.
Custom avatar upload
Section titled “Custom avatar upload”Merchant flow
Section titled “Merchant flow”- Open Appearance.
- Click the Upload circle (next to preset avatars).
- Choose JPEG, PNG, WebP, or GIF (max 2 MB).
- Server optimizes and selects custom automatically.
- Click Save on the appearance form to persist other settings (preset
customis stored on upload).
Remove custom image clears the file and sets preset to none.
Server processing (app/lib/chat-avatar.server.js)
Section titled “Server processing (app/lib/chat-avatar.server.js)”- Sharp: auto-rotate (EXIF), crop to square, resize to 512×512, encode WebP (quality ~82).
- Typical output: ~15–40 KB per shop (header displays at 36px; larger asset avoids blur when scaled down).
- Saved to:
public/chat-avatars/custom/{shopId}.webp(gitignored;.gitkeeponly in repo). - DB:
shops.chatbot_avatar_preset = 'custom',shops.chatbot_avatar_updated_atfor cache-busting.
Public URL
Section titled “Public URL”Built by buildChatbotAvatarUrl('custom', baseUrl, { shopId, avatarVersion }):
https://{SHOPIFY_APP_URL}/chat-avatars/custom/{shopId}.webp?v={timestamp}Returned to the storefront as avatarUrl from GET /api/widget-settings.
Serving in production: uploads are stored under public/chat-avatars/custom/ (not in build/client). The app route chat-avatars.custom.$filename.jsx reads those files from disk. Preset PNGs still come from the static build/client/chat-avatars/ bundle.
Persistence: mount a volume on /app/public/chat-avatars/custom in Docker (or equivalent) so uploads survive redeploys.
Storefront widget
Section titled “Storefront widget”extensions/appifire-chat/assets/chat-widget.js:
- Prefers
settings.avatarUrlfrom the API (works for custom WebP and cache-bust query). - Header image:
width="36" height="36"(CSS); source file is 512×512 WebP for crisp downscaling.loading="lazy" decoding="async".
Database
Section titled “Database”| Column | Type | Notes |
|---|---|---|
chatbot_avatar_preset | string | none, custom, or 1–5 |
chatbot_avatar_updated_at | timestamp | Set on each successful custom upload |
widget_launcher_style | string | text (default) or 1–15 |
Migration: 20260523140000_add_chatbot_avatar_updated_at, 20260531120000_add_widget_launcher_style.
Dependencies & deploy
Section titled “Dependencies & deploy”npm install # includes sharpnpx prisma migrate deployshopify app deploy # if theme extension JS changedUploaded files live on the app server filesystem under public/chat-avatars/custom/. Back up or persist that directory in production if you rely on custom avatars across deploys (or plan object storage later).
Files summary
Section titled “Files summary”| File | Role |
|---|---|
app/lib/chat-avatar.server.js | Validate, resize, save, delete custom avatar |
app/lib/widget-appearance.js | Preset custom, buildChatbotAvatarUrl, launcher style normalization and display order |
app/routes/app.appearance.avatar.jsx | Admin upload/remove action |
app/routes/app.appearance.jsx | UI + loader custom preview URL + launcher style picker |
app/routes/api.widget-settings.jsx | Exposes avatarUrl, widgetLauncherStyle, launcherButtonUrl to storefront |
app/routes/chat-avatars.custom.$filename.jsx | Serves {shopId}.webp from disk in production |
public/chat-buttons/ | Preset launcher button PNGs (1.png–15.png) |
extensions/appifire-chat/assets/chat-widget.js | Renders header avatar and launcher button from API settings |