GLPI plugin — Generate personalized corporate PNG email signatures for every GLPI user
Each signature is rendered dynamically over a configurable PNG template using PHP GD and Avenir TTF or custom fonts, with an optional WhatsApp QR code. Finished signatures can be downloaded directly or sent to the user's registered email address in one click.
| Feature | Details |
|---|---|
| Two PNG templates | One for users with a mobile number, one without. Template selection is automatic based on the user's mobile field. |
| Dynamic text overlay | Name, title, email, phone, extension, Facebook, X (Twitter), LinkedIn, Instagram, Snapchat, TikTok, corporate website — rendered with PHP GD using Avenir Black/Roman TTF fonts. |
| Name auto-fit | Font size is adjusted up and down (within the configured range) so short names use the full allocated width and long names never overflow. |
| WhatsApp QR code | Generated with TCPDF (bundled with GLPI). Module size configurable (1–10 px/module) from the General settings tab; rendered at native resolution with no resampling for a crisp result. |
| Download | PNG generated on the fly and streamed as a browser download — no permanent file stored. |
| Email delivery | Sends the PNG as an attachment using GLPI's outgoing mail system (GLPIMailer). |
| Admin test email | Sends a sample to the administrator's own address with [PRUEBA] prefix and warning banner. |
| Signature preview | Opens the final PNG in a modal before downloading or sending. |
| Inline email formatting | Body and footer fields support **bold**, *italic* and __underline__ markers — rendered as inline CSS styles compatible with Outlook and Gmail. A B / I / U toolbar wraps or unwraps selected text in one click. |
| Visual position editor | Drag-and-drop fields over the live template image. Mouse and touch (tablet) support. |
| Scale-aware editor | Field coordinates are stored in GD pixel space and translated to CSS on every tab-open and window resize, keeping overlays aligned at any screen width. |
| Font size per field | Number input per field in the editor table; overlay updates live. |
| Clickable variable badges | {nombre}, {empresa}, {fecha} insert at the cursor in the last focused email field. |
| Unsaved changes indicator | Orange dot on the Positions tab + warning banner when positions are changed but not yet saved. |
| Upload validation | Only PNG files up to 300 KB are accepted (hard limit). Recommended dimensions are 650×250 px, shown as a hint below the file input. |
| Incomplete config badge | Orange ! badge on the user profile tab when the relevant template is missing or email is not configured. |
| Independent delete forms | Delete-template buttons use their own <form> with dedicated CSRF tokens, decoupled from the main config form. |
| CSRF protection | All POST endpoints use GLPI's built-in CSRF token validation. |
| Custom fonts | Upload TTF or OTF font files from the Fonts tab. The plugin reads each file's internal name table to display its real name. Built-in Avenir Black and Avenir Roman are always available. |
| Per-role font selection | Choose Name font (used for the signature name) and Body font (used for all other fields) independently. Both built-in and uploaded fonts are available for either role. |
| Per-field visibility toggle | Checkbox next to each field in the position editor enables or disables that field independently per template. Hidden fields are skipped during PNG generation — no blank space left. |
GLPI mail log integration — Write tagged entries to files/_log/mail.log, through Toolbox::logInFile(). |
|
| Multilanguage | es_MX · fr_FR · Default languague: English |
| Requirement | Minimum |
|---|---|
| GLPI | ≥ 11.0. |
| PHP | ≥ 8.2 |
| PHP ext: GD | Required — image generation |
| PHP ext: fileinfo | Required — MIME validation on template upload |
| PHP ext: mbstring | Recommended (used internally by GLPI) |
| TCPDF | Bundled with GLPI — required only for QR code |
| Outgoing mail server | GLPI → Setup → Notifications — required only for email delivery |
- Download the latest
.zipfrom the GitHub releases page. - In GLPI, go to Setup → Plugins.
- Click Upload a plugin.
- Select the ZIP and confirm.
- Click Install next to Email Signatures.
- Click Enable to activate.
cd /var/www/glpi/plugins
unzip signatures-X.X.X.zip
# The folder must be named exactly "signatures"Then go to Setup → Plugins, install, and enable.
Important: the plugin directory must be named
signatures(lowercase).
- Go to Setup → Plugins.
- Click Disable next to Email Signatures, then Uninstall.
Uninstalling removes all plugin_signatures keys from glpi_configs.
PNG template files on disk are NOT deleted — they remain in
plugins/signatures/templates/ and will be reused if the plugin is reinstalled.
Go to Setup → Plugins → Email Signatures → Configure. The page has five tabs.
| Field | Config key | Notes |
|---|---|---|
| Facebook page | facebook_page |
Page name or handle (e.g. MiEmpresa). Leave empty to omit. |
| X (Twitter) handle | x_page |
Handle with or without @ (e.g. @AcmeCorp). Leave empty to omit. |
| LinkedIn page | linkedin_page |
LinkedIn slug or URL path (e.g. company/acmecorp). Leave empty to omit. |
| Instagram handle | instagram_page |
Handle with or without @. Leave empty to omit. |
| Snapchat username | snapchat_page |
Username only, no URL. Leave empty to omit. |
| TikTok handle | tiktok_page |
Handle with or without @. Leave empty to omit. |
Each social media field is rendered as plain text on the PNG signature. Position and font size are configurable per template in the Positions tab. Fields are only rendered if the configured value is non-empty.
Numeric country code without + (e.g. 52 for Mexico, 1 for USA/Canada,
34 for Spain). Used to build https://wa.me/{code}{mobile} encoded in the QR.
If this field is empty, no QR code will be generated regardless of whether the user has a mobile number.
These fields define the email sent when a user clicks Send by email.
| Field | Notes |
|---|---|
| Email subject | Plain text. Supports {nombre}, {empresa}, {fecha}. |
| Email body | HTML. Supports variables and **bold** markdown. |
| Email footer | Optional footer paragraph. Same variable and markdown support. |
Click any variable badge to insert it at the cursor in the last focused field.
Bold markdown: wrap text in **double asterisks** to render it as
<span style="font-weight:bold"> — compatible with Outlook, Gmail, and most
webmail clients.
Sends a copy of the currently saved configuration to the administrator's own
registered email address. The subject is prefixed with [PRUEBA] and the body
includes a yellow warning banner.
Save the form first if you want to test new wording — the button uses saved values, not the current unsaved content of the form fields.
Manages base.png — the template used for users who have a mobile number set in
their GLPI profile.
| Element | Description |
|---|---|
| Current template | Shows the active base.png at full responsive width. Click the image to download it. |
| Upload new | PNG only, max 300 KB (hard limit enforced server-side). MIME validated with finfo. A browser preview is shown before saving. Recommended dimensions: 650×250 px (shown as a hint). |
| Delete | Removes base.png from disk. Uses a dedicated mini-form with its own CSRF token so the main configuration is not affected. |
Template design tips:
- Recommended size: 650 × 250 px at 96 dpi.
- Leave blank or light-colored areas where text fields will appear (name, title, etc.).
- PNG with transparency is supported.
- Avoid placing important graphic elements in the text regions; they will be covered.
- Design the "Without mobile" template to omit the mobile and QR column.
Identical to "With mobile" but manages base2.png, used for users without a mobile
number. Typically has a slightly different layout without the WhatsApp QR area.
Live drag-and-drop editor for field placement per template.
- Open the tab — the editor initializes. The background image renders at responsive width and all field overlays are scaled to match.
- Toggle fields — check or uncheck the checkbox at the start of each row to show or hide that field. Hidden fields disappear from the canvas and are skipped during PNG generation.
- Drag fields — grab any labeled overlay with the mouse or a finger (touch supported). The overlay moves and the hidden GD-coordinate inputs update in real time.
- Adjust font size — use the number input in the table below the editor. The overlay font size updates immediately.
- Read coordinates — the Position column in the table shows X,Y in GD space (natural image pixels) — the exact values that will be stored.
- Reset — click Restore default positions to revert all fields for that template to factory defaults.
- Save — click Save at the bottom right. The button is disabled and shows a spinner while submitting to prevent double clicks.
An orange dot appears on the tab label and a warning banner shows at the bottom of the page whenever positions have been changed but not yet saved.
All coordinates are stored and displayed in GD pixel space (the natural pixel
size of the PNG template, typically 650×250 px). When the tab opens or the window
is resized, applyScale() multiplies the GD coordinates by
img.clientWidth / img.naturalWidth to get CSS pixel positions for the overlays.
When a field is dragged, syncInputs() divides the CSS position back by the same
ratio before updating the hidden inputs. This two-way conversion keeps the visual
editor and the stored values consistent at any display size.
| Key | Rendered content | Font |
|---|---|---|
nombre |
User full name (getFriendlyName()) |
Avenir Black |
titulo |
User title / position (from glpi_usertitles) |
Avenir Roman |
email |
Primary email (glpi_useremails, is_default=1) |
Avenir Roman |
mobile |
User's mobile number | Avenir Roman |
tel |
Entity phone number | Avenir Roman |
ext |
phone2 if set, otherwise phone |
Avenir Roman |
facebook |
Facebook page name (from plugin config) | Avenir Roman |
web |
Corporate website (from entity) | Avenir Roman |
x |
X (Twitter) handle (from plugin config) | Avenir Roman |
linkedin |
LinkedIn page slug (from plugin config) | Avenir Roman |
instagram |
Instagram handle (from plugin config) | Avenir Roman |
snapchat |
Snapchat username (from plugin config) | Avenir Roman |
tiktok |
TikTok handle (from plugin config) | Avenir Roman |
qr |
WhatsApp QR code image (composited, not text) | — |
| Key | Rendered content | Font |
|---|---|---|
nombre |
User full name | Avenir Black |
titulo |
User title / position | Avenir Roman |
email |
Primary email address | Avenir Roman |
tel |
Entity phone number | Avenir Roman |
ext |
Extension or office phone | Avenir Roman |
facebook |
Facebook page name | Avenir Roman |
web |
Corporate website | Avenir Roman |
x |
X (Twitter) handle | Avenir Roman |
linkedin |
LinkedIn page slug | Avenir Roman |
instagram |
Instagram handle | Avenir Roman |
snapchat |
Snapchat username | Avenir Roman |
tiktok |
TikTok handle | Avenir Roman |
Upload and manage custom fonts for signature rendering.
| Element | Description |
|---|---|
| Upload font | TTF or OTF files, max 2 MB. The plugin reads the file's internal name table and shows the real font name everywhere (selects, table). |
| Name font | Font used to render the user's name in the signature. Defaults to Avenir Black. |
| Body font | Font used for all other fields (title, email, phone, etc.). Defaults to Avenir Roman. |
| Installed fonts | Table of uploaded fonts showing real name, filename, current role badge, and a delete button. |
Avenir Black and Avenir Roman are always available as built-in options and cannot be deleted. Both fonts are available for either role — you can assign Avenir Roman as the name font or Avenir Black as the body font if needed.
| Variable | Resolved from |
|---|---|
{nombre} |
User::getFriendlyName() (first + last name) |
{empresa} |
Entity::name for the user's entity. Falls back to the root entity name or $CFG_GLPI['name'] for root-entity users. |
{fecha} |
date('d/m/Y') at the moment the email is sent |
Every GLPI user profile shows an Email Signature tab registered by
PluginSignaturesUser. The tab is visible to the owner and to administrators.
An orange
!badge on the tab means the relevant template has not been uploaded yet, or the outgoing email configuration is incomplete. Buttons are shown but generation will fail with a descriptive error message.
Click Download signature to generate the PNG and save it to your computer.
- The PNG is generated on the fly — no file is stored permanently on the server.
- File name format:
signature_{name}_{userid}.png. - Include QR checkbox: shown only if the user has a mobile number. Controls whether the WhatsApp QR is included in this specific download.
How to install the signature in your email client:
| Client | Steps |
|---|---|
| Outlook (desktop) | File → Options → Mail → Signatures → New. In the editor: Insert → Picture → select the PNG. |
| Outlook (web / OWA) | Settings → View all → Mail → Compose and reply → Signature → image icon → upload the PNG. |
| Gmail | Settings (gear) → See all settings → General → Signature → New → image icon → upload. |
| Apple Mail | Mail → Preferences → Signatures. Drag the PNG into the signature editor. |
| Thunderbird | Account Settings → Signature text → Attach signature from file. Select the saved PNG. |
Click Send by email to deliver the PNG directly to the email address registered
in the user's GLPI profile (glpi_useremails, is_default = 1).
- The signature is generated, attached, sent, and the temporary file is deleted immediately — nothing is stored on the server.
- Subject, body, and footer come from the plugin configuration with variables substituted at send time.
- A success flash message shows the destination address.
- Errors (no email, mail server not configured, generation failure) show descriptive
flash messages and are logged via
Toolbox::logError().
Click Preview to open a modal showing the final rendered PNG without downloading it. Useful for verifying the layout before sending.
Does the user have a mobile number?
├─ YES → base.png (With mobile template)
└─ NO → base2.png (Without mobile template)
If the selected template file does not exist, generation stops and the user sees a descriptive error message pointing to the configuration page.
All coordinates and font sizes come from glpi_configs (set in the Positions tab),
with per-key fallback to built-in defaults if not yet configured.
Fields marked b1 only appear in the With-mobile template; all others appear in both templates. Each field can be independently enabled or disabled per template from the Positions tab.
| Field | Source | Default size | Template |
|---|---|---|---|
| Name | User::getFriendlyName() |
40 px (auto-fit) | both |
| Title | glpi_usertitles via usertitles_id |
11 px | both |
glpi_useremails (is_default=1) |
11 px | both | |
| Mobile | User::fields['mobile'] |
11 px | b1 only |
| Entity phone | Entity::phonenumber |
11 px | both |
| Extension | User::fields['phone2'] if set, else phone |
11 px | both |
plugin_signatures.facebook_page config |
11 px | both | |
| Website | Entity::website |
11 px | both |
| X (Twitter) | plugin_signatures.x_page config |
11 px | both |
plugin_signatures.linkedin_page config |
11 px | both | |
plugin_signatures.instagram_page config |
11 px | both | |
| Snapchat | plugin_signatures.snapchat_page config |
11 px | both |
| TikTok | plugin_signatures.tiktok_page config |
11 px | both |
| QR code | TCPDF2DBarcode → imagecopyresampled | 100×100 px | b1 only |
The active font for each role is resolved at generation time:
- If a custom font is configured and its file exists in
GLPI_PLUGIN_DOC_DIR/signatures/fonts/→ use it. - If a built-in font is explicitly selected (
AvenirBlack.ttforAvenirRoman.ttf) → use the bundled file. - Otherwise → fall back to the default built-in (Avenir Black for name, Avenir Roman for body).
- Start at the configured maximum size (default 40 px).
- Measure text width with
imagettfbbox(). - If the text exceeds available width → decrease by 1 px, repeat.
- If the text fits and there is room → increase by 1 px (up to the maximum), repeat.
This guarantees short names like "Ana" render at full size while long names like "María Fernanda Rodríguez Bustamante" are reduced without overflow.
- Build
https://wa.me/{countryCode}{mobile}(non-numeric characters stripped frommobile). TCPDF2DBarcodegenerates a QR image into a temp file.- Load the QR with
imagecreatefrompng(). - Composite onto the signature with
imagecopyresampled()usingimagesx()/imagesy()for actual dimensions — not a hardcoded 100 px, preventing cropping. - Temp file deleted immediately after compositing.
| Action | Who |
|---|---|
| Download own signature | Any authenticated user |
| Download another user's signature | config UPDATE right |
| Send own signature by email | Any authenticated user |
| Send another user's signature by email | config UPDATE right |
| Access plugin configuration page | config UPDATE right |
| Upload / delete templates | config UPDATE right |
| Send test email | config UPDATE right |
Access checks use Session::haveRight('config', UPDATE) and
Session::checkRight(). Unauthorized requests redirect silently to the GLPI root.
| Code | Language |
|---|---|
es_MX |
Spanish (Mexico) |
fr_FR |
French (France) |
Default language is English.
To add a new language:
# 1. Copy the template
cp locales/signatures.pot locales/de_DE.po
# 2. Edit the .po file (fill in msgstr values)
# Use Poedit, Virtaal, or any PO editor
# 3. Compile
msgfmt locales/de_DE.po -o locales/de_DE.mosignatures/
├── front/
│ ├── config.form.php Plugin configuration UI (5-tab page)
│ ├── download.php Generates and streams the PNG download
│ ├── resource.send.php Serves template PNGs to the browser
│ └── send.php Sends signature by email (normal + test mode via is_test=1)
├── inc/
│ ├── config.class.php glpi_configs read/write wrapper (request-cached)
│ ├── paths.class.php Centralizes all file paths and public URLs
│ ├── renderer.class.php Standalone Twig renderer (rooted at plugin /templates/)
│ ├── signature.class.php Image generation, email assembly, QR composition
│ └── user.class.php GLPI tab registration, user-facing UI
├── locales/
│ ├── signatures.pot Translation template
│ ├── es_MX.po / es_MX.mo
│ └── fr_FR.po / fr_FR.mo
├── public/
│ └── js/
│ │ ├── signatures-config.js Config page: drag-and-drop editor, tab routing, field toggles
│ │ └── signatures-user.js User tab: preview modal, download, send-by-email handlers
│ └── fonts/
│ ├── AvenirBlack.ttf Built-in name font (Avenir Black)
│ └── AvenirRoman.ttf Built-in body font (Avenir Roman)
├── templates/
│ ├── config_form.html.twig Config page markup (5 tabs: General, With mobile, Without mobile, Positions, Fonts)
│ └── user_tab.html.twig User profile tab markup (Vue 3 components + preview modal)
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── logo.png Plugin icon
├── plugin.xml GLPI marketplace manifest
├── README.md This file
└── setup.php Registration, install/uninstall/update hooks
The
GLPI_PLUGIN_DOC_DIR/signatures/directory (outside the plugin folder) is auto-created on install and stores uploaded PNG templates (templates/) and custom fonts (fonts/). It is excluded from the distribution ZIP and is NOT removed on uninstall.
See CHANGELOG.md.
Edwin Elias Alvarez — GitHub.
If you like my work, you can support me with a donation:
GPL-3.0-or-later — see LICENSE.
Report bugs or request features on the issue tracker.

