Share This Article
Automating workflows is a cornerstone of modern efficiency, especially for tech enthusiasts, marketers, and founders looking to streamline operations. Tools like n8n provide incredible power to connect different applications and services, moving data seamlessly between them. However, a common challenge arises when you need to share structured information, specifically when attempting the task of Sending Table Data to Telegram Using n8n. While n8n excels at data manipulation and API interactions, Telegram itself presents unique hurdles when it comes to displaying nicely formatted tables directly within chat messages. Many users in the n8n community have grappled with taking data from sources like databases or spreadsheets and presenting it clearly in Telegram. This guide dives deep into why this is challenging and explores practical, step-by-step solutions you can implement directly within your n8n workflows to overcome this limitation.
Understanding the Core Challenge: Telegram’s Message Formatting Limitations
Before diving into solutions within n8n, it’s crucial to understand the root cause of the difficulty. Telegram allows messages to be formatted using either Markdown or a subset of HTML. The n8n Telegram node conveniently offers a “Parse Mode” option where you can select “HTML” to enable rich text formatting like bold (<b>
), italics (<i>
), code blocks (<code>
), and pre-formatted text (<pre>
).
However, Telegram’s implementation of HTML is limited. Crucially, it does not support HTML table tags – specifically <table>
, <tr>
, <th>
, and <td>
. Sending a message containing these tags, even with HTML Parse Mode enabled in the n8n Telegram node, will not render a visual table. Instead, Telegram will either display the raw HTML tags as text or strip them out entirely, leaving you with unformatted, often jumbled data. This isn’t a limitation of n8n itself, but rather a constraint imposed by the Telegram messaging platform’s rendering capabilities.
This fundamental limitation means we need creative workarounds within our n8n workflows to achieve a table-like presentation in Telegram messages. Fortunately, n8n’s flexibility provides several ways to tackle this.
Method 1: Using Pre-formatted Text (<pre>
) with the n8n Code Node
One common approach leverages Telegram’s support for the <pre>
HTML tag. This tag is intended for displaying pre-formatted text, meaning whitespace (spaces, tabs, newlines) is preserved, and the text is typically rendered in a monospace font (where each character has the same width, like Courier New or Consolas). By carefully constructing text with strategically placed spaces, you can simulate the appearance of columns and rows.
The Concept
The idea is to use an n8n Code Node (or potentially a Function node) to take your input data (usually an array of JSON objects) and manually build a single string. This string will contain your table headers and data rows, with spaces added to align the columns visually. The entire string is then wrapped in <pre>...</pre>
tags before being sent to the Telegram node.
Step-by-Step n8n Implementation
- Input Data: Ensure your data arrives at the Code Node as an array of objects. For example, data from a Spreadsheet node or an HTTP Request node returning JSON might look like this:
[ { "product": "Laptop Pro", "stock": 15, "status": "Active" }, { "product": "Wireless Mouse", "stock": 125, "status": "Active" }, { "product": "Keyboard", "stock": 0, "status": "Inactive" }]
- Add a Code Node: Drag and drop a Code Node onto your n8n workflow canvas and connect the node providing the data array to its input.
- Write the JavaScript Code: Open the Code Node settings and paste in JavaScript code similar to the following. This example assumes the input data comes from the node directly connected to the Code node’s input.
// Access the array of items from the inputconst items = $input.all();// --- Configuration ---// Define column headers and the corresponding JSON keysconst columns = [ { header: 'Product Name', key: 'product', width: 20 }, { header: 'Stock Level', key: 'stock', width: 12 }, { header: 'Status', key: 'status', width: 10 }];const columnSeparator = ' | '; // Visual separator between columns// --- End Configuration ---// Helper function for padding stringsconst pad = (str, length) => String(str).padEnd(length);// Build the header stringlet headerString = '';columns.forEach(col => { headerString += pad(col.header, col.width) + columnSeparator;});// Remove trailing separatorheaderString = headerString.slice(0, -columnSeparator.length);// Build the separator line (optional, for visual clarity)let separatorLine = '';columns.forEach(col => { separatorLine += '-'.repeat(col.width) + columnSeparator.replace(/\|/g, '+'); // Use '+' for intersection});separatorLine = separatorLine.slice(0, -columnSeparator.length);// Build the data rows stringlet dataRowsString = '';for (const item of items) { let rowString = ''; columns.forEach(col => { // Safely access nested properties if needed, e.g., item.json.data.fieldName const value = item.json[col.key] !== undefined ? item.json[col.key] : ''; rowString += pad(value, col.width) + columnSeparator; }); // Remove trailing separator and add newline dataRowsString += rowString.slice(0, -columnSeparator.length) + '\n';}// Remove trailing newlinedataRowsString = dataRowsString.trim();// Combine header, separator, and rows, wrapping in <pre> tagsconst finalMessage = `<pre>${headerString}\n${separatorLine}\n${dataRowsString}</pre>`;// Return the message in the structure n8n expects// Use a distinct key like 'telegramMessage'return [{ telegramMessage: finalMessage }];
Explanation:
- We define the columns, their headers, the corresponding keys in the JSON data, and a desired width for padding.
- The
padEnd()
string method is crucial here. It adds spaces to the end of each cell’s content until it reaches the specifiedwidth
, ensuring alignment. - We loop through the input items (
items
array). For each item, we loop through our defined columns, extract the data using thekey
, pad it, and append it to therowString
. - Headers, an optional separator line, and data rows are combined.
- The entire constructed string is wrapped in
<pre>...</pre>
tags. - Finally, we return an object structured correctly for the next node, using a key like
telegramMessage
.
- Configure the Telegram Node:
- Add a Telegram node after the Code Node.
- Set the “Chat ID” to your target Telegram chat or user.
- In the “Text” field, use an n8n expression to reference the output of the Code Node. If you used
telegramMessage
as the key in the Code Node’s return statement, the expression would be:{{ $('Code').item.json.telegramMessage }}
(adjust ‘Code’ if your node has a different name). - Crucially, set the “Parse Mode” parameter to
HTML
. This tells Telegram to interpret the<pre>
tags.
Pros and Cons of the <pre>
Tag Method
Pros:
- Self-Contained: The entire logic resides within your n8n workflow; no external services or dependencies are needed.
- Relatively Simple: While it requires some JavaScript, the logic is straightforward for basic tables.
- No External Costs: Doesn’t rely on potentially paid external APIs.
- Text-Based: The content remains text, which might be preferable in some contexts (e.g., copy-pasting parts of it, although formatting might be lost).
Cons:
- Font Dependency: Relies heavily on the end user’s Telegram client rendering the text within
<pre>
tags using a consistent monospace font. If a proportional font is used, the alignment will break completely. - Fragile Alignment: Column alignment achieved through spaces can look poor or misaligned on different screen sizes, especially narrow mobile devices where text wrapping might occur unexpectedly within the
<pre>
block. - Manual Width Adjustment: You need to manually define column widths (
width
in the code). If your data content varies significantly in length, you might need to dynamically calculate widths or risk truncation or excessive whitespace. - Not a True Table: It’s a visual simulation, not a semantic table. It lacks features like proper cell structure or borders beyond simple characters.
- Readability for Complex Data: Can become visually cluttered and hard to read with many columns or long text entries.
Method 2: Generating and Sending a Table Image
For a more robust and visually consistent solution, you can generate an image of your table data outside of Telegram’s text rendering limitations and then send that image using the n8n Telegram node.
The Concept
This method involves using an n8n HTTP Request Node to communicate with an external web service or API that specializes in converting structured data (like JSON) into an image (e.g., PNG, JPG). Your n8n workflow prepares the data, sends it to the API, receives the generated image URL or binary data back, and then uses the Telegram node’s “Send Photo” operation to deliver the image to your chat.
Leveraging External Services (Example Concept: Table-to-Image APIs)
Several online services can achieve this. A conceptual example is an API like QuickChart’s table endpoint, but others exist, sometimes requiring libraries or specific setups. These services typically work as follows:
- You send a POST request to the service’s API endpoint.
- The request body contains your table data, often structured as JSON, along with potential styling or formatting options (like colors, fonts, etc., depending on the service).
- The API processes this data, renders it as a table within an image canvas.
- The API response usually contains either a URL pointing to the generated image or the binary data of the image itself.
You’ll need to consult the documentation of the specific service you choose to understand its required data format, API endpoint, and response structure.
Step-by-Step n8n Implementation
- Prepare Data: Ensure your data is in the correct JSON format required by the external table-to-image API you’ve chosen. You might need a Function Node or Code Node before the HTTP Request node to transform your initial data (e.g., from a database query) into the specific structure the API expects.
- Configure the HTTP Request Node:
- Add an HTTP Request node after your data preparation step.
- Set “Method” to
POST
. - Set “URL” to the API endpoint of the table-to-image service.
- In the “Body” tab, select “Raw/Custom”.
- Enter the JSON payload required by the API. You’ll likely use n8n expressions here to dynamically insert your table data from previous nodes. Example structure (highly dependent on the API):
{ "data": {{ JSON.stringify($('PreviousNodeName').all().map(item => item.json)) }}, "options": { "title": "Inventory Report", "theme": "light" // ... other API-specific options }}
- Under “Options”, ensure “Response Format” is set appropriately. If the API returns a URL,
JSON
might be suitable. If it returns the raw image binary data, set it toFile
.
- Handle the API Response:
- If the API returns a URL: The URL will likely be in the JSON response body. You can access it using an expression in the next node, for example:
{{ $('HttpRequest').item.json.imageUrl }}
(adjust ‘HttpRequest’ and ‘imageUrl’ based on your node name and the actual API response structure). - If the API returns binary data (Response Format = File): The image data will be available in the ‘binary’ property of the output item. n8n automatically handles this when connected to a node expecting binary data.
- If the API returns a URL: The URL will likely be in the JSON response body. You can access it using an expression in the next node, for example:
- Configure the Telegram Node:
- Add a Telegram node after the HTTP Request Node.
- Set the “Chat ID”.
- Change the “Operation” parameter to
Send Photo
. - In the “Photo” field:
- If you received a URL, enter the expression referencing that URL:
{{ $('HttpRequest').item.json.imageUrl }}
. - If you received binary data (HTTP Request ‘Response Format’ was ‘File’), you can directly connect the output of the HTTP Request node. The binary data property (usually named ‘data’ by default when Response Format is File) will be automatically used. You can specify the input binary data field name if needed.
- If you received a URL, enter the expression referencing that URL:
- Optionally, add text to the “Caption” field to accompany the image.
Pros and Cons of the Image Method
Pros:
- Visually Consistent: The primary advantage. The table image will look identical across all Telegram clients and devices (desktop, mobile, web) because it’s a static image.
- Professional Appearance: Images generally look more polished and professional than text-based tables, especially with styling options offered by some APIs.
- Handles Complexity Well: Better suited for tables with many columns, rows, or complex formatting that would be unmanageable with the
<pre>
tag method. - Rich Formatting Potential: Many APIs allow customization of colors, fonts, cell merging, etc., offering much richer visual options.
Cons:
- External Dependency: Relies on a third-party service. If that service goes down, has limitations, or changes its API, your workflow breaks.
- Potential Costs/Limits: External services may have usage limits on free tiers or require paid subscriptions for higher volumes or advanced features. (*Note: We avoid discussing specific pricing here.*)
- Increased Complexity: Setting up the HTTP Request node, understanding the external API’s requirements, and handling the response adds complexity compared to the Code Node method.
- Not Searchable/Selectable: The table content is part of an image, so users cannot easily copy/paste text from it or search for specific data within the Telegram chat history based on the table content.
- Data Transfer: You are sending your potentially sensitive table data to an external third-party service for processing. Ensure you understand and are comfortable with the service’s privacy policy.
Key n8n Tips for Implementing These Solutions
Regardless of the method you choose, certain n8n concepts are crucial for success:
Accessing Data Correctly in Nodes
Understanding how to reference data from previous nodes is fundamental in n8n. Within nodes like the Code Node or in expression editors:
$input.item
(newer syntax often used in Code node): Refers to the single input item being processed in that specific iteration (when the node processes items one by one). Access JSON data via$input.item.json.fieldName
.$input.all()
oritems
(older Code node context): Represents the entire array of input items. You’ll typically loop through this array (e.g.,for (const item of items) { ... item.json.fieldName ... }
).$('Node Name').item.json.fieldName
(Expressions): Used in parameter fields (like the Telegram node’s “Text” or “Photo” field) to reference data from a specific previous node named ‘Node Name’. This typically accesses the first item’s data unless the node outputs multiple items and the subsequent node is configured to handle them.$('Node Name').all()
(Expressions): Can be used in expressions to get all output items from a node, often requiring methods like.map()
or.filter()
if used in places expecting a single value.
Always inspect the input data view in a node to confirm the exact structure and how to access the fields you need (e.g., item.json.column_name
vs. item.json.data.property
).
Sorting Your Table Data
If the order of rows in your Telegram message matters (e.g., sorting by product name, stock level, or date), you need to sort the data before it gets formatted by the Code Node or sent to the image generation API.
- Use the Item Lists node.
- Set the “Operation” to
Sort
. - Specify the “Field to Sort By” using the correct path to the data field within the JSON structure (e.g.,
json.stock
orjson.product
). - Choose the “Sort Order” (Ascending/Descending) and “Sort Type” (Alphanumeric, Numeric, Date).
- Place this node directly before your Code Node or the node that prepares data for the HTTP Request.
Structuring Node Output Correctly
Nodes in n8n generally expect input data as an array of items, where each item has a json
property containing the actual data object. When creating data in a Code Node or Function Node, ensure your return
statement adheres to this structure.
- Code Node Example Return:
// Assuming 'finalMessage' holds the string generated for method 1return [{ json: { telegramMessage: finalMessage } }];// Or simply (n8n often wraps it correctly):return [{ telegramMessage: finalMessage }]; // Check node output to confirm
- HTTP Request Node Output (JSON Response): The node automatically structures the parsed JSON response within the
json
property of its output item(s). - HTTP Request Node Output (File Response): The node outputs an item with a
binary
property containing the file data, ready to be consumed by nodes like Telegram’s “Send Photo”.
Always check the output view of your nodes to ensure the data is structured as expected by the subsequent nodes in your workflow.
Which Method Should You Choose?
The best approach for sending table data to Telegram using n8n depends entirely on your specific needs and priorities:
- Choose the
<pre>
Tag Method (Code Node) if:- Your data is relatively simple (few columns, short text).
- You want a quick solution without external dependencies.
- Absolute visual consistency across all devices isn’t paramount, or you know your audience primarily uses clients with good monospace font support.
- You prefer keeping the logic entirely within n8n.
- Choose the Image Generation Method (HTTP Request Node) if:
- Visual consistency and a professional appearance are critical.
- Your tables are complex, have many columns, or require specific styling.
- You are comfortable setting up an HTTP Request and potentially relying on an external service (and its associated considerations).
- The inability to copy/paste text directly from the table image is acceptable.
Consider factors like the complexity of your data, how critical perfect formatting is, the technical expertise available, and whether introducing an external dependency is acceptable for your use case.
Conclusion: Overcoming Telegram’s Table Limitations with n8n
While Telegram’s lack of native HTML table support presents a hurdle, it’s far from insurmountable when using a powerful automation tool like n8n. The challenge of Sending Table Data to Telegram Using n8n can be effectively addressed through creative workarounds.
We’ve explored two primary solutions: manually formatting text using spaces within <pre>
tags via the Code Node, and leveraging external APIs through the HTTP Request Node to generate and send table images. Each method comes with its own set of advantages and disadvantages regarding visual fidelity, implementation complexity, and dependencies.
By understanding Telegram’s limitations and mastering n8n’s capabilities—particularly the Code Node, HTTP Request Node, Item Lists node, and data referencing—you can choose and implement the solution that best fits your reporting and notification needs. Don’t hesitate to experiment with both approaches in your n8n workflows to see which one delivers the optimal result for your specific data and audience. Happy automating!