Repository: Avaiga/taipy
Pull request: table format function
resolves #564

```
import pandas as pd

from taipy.gui import Gui

data = pd.DataFrame()

# Create 1O columns with 100 rows of bools
for i in range(10):
    data[f"col_{i}"] = list(range(100))


def my_format(state, value, index, row, col_name):
    return f"value: {value} for {index} in {col_name}"


page = """
<|{data}|table|format_fn[col_2]=my_format|not editable|>
"""
if __name__ == "__main__":
    Gui(page).run(title="564 table format function")

```

Problem statement:
Add formatting in tables using functions
Currently Taipy uses printf style formatting in tables
~~~ python
<|table|data={my_data}|columns={col_format}|...|>
col_format = {'Sales':'%.2f'}
~~~

This could be extended by allowing functions, like in <|selector|adapter={...}> 

~~~ python
col_format={ "Sales": {"format_function": format_func}}

def format_func(data, column_name) -> list[str]:
    ... apply formatting ...
    return formatted_column_data
~~~

Such functions might even return images 


Background:  
I'd like to have floats formatted like 9.999,99 which seems not possible using pure printf style syntax in Taipy.
Taipy doesn't support %'d to get locale aware thousands separator.
AFAIK printf doesn't support thousands separators in floats.

Hints from issue discussion:
Thank you for your issue! It seems like a good feature! We will talk about it inside our R&D team
Hey @FlorianJacta, is this feature required, may I give it a try to add this?
@FabienLelaquais @FredLL-Avaiga Could this issue be assigned?
It certainly can. The thing is, it is far more complicated than it looks since the conversion to string has to be performed on the server side, which is going to be a problem. Especially for the edition part.
But hey, let's move forward yes.
Hey @FabienLelaquais @FlorianJacta , I've a doubt in this, like instead of letting the user provide the format function, can we provide our own format functions? like, we can have some functions which renders a specific number of rows that the user requires or something like that?

If the user passes the format function, then will it be possible to implement that?
I am not sure I understand your issue @yaten2302. We want a function that converts all values of the rows of a column into other values that will be the ones displayed.
@FlorianJacta actually, I'm not completely able to understand this? Could you please explain this once again - "We want a function that converts all values of the rows of a column into other values that will be the ones displayed."?
We want to use a user function to format column values. Just, like the example written in the issue.

Maybe you already understand it this way, and I am just overcomplicating it.

Commit message: Update config.pyi

Patch to review:
diff --git a/frontend/taipy-gui/package-lock.json b/frontend/taipy-gui/package-lock.json
index ae9654a7ca..4c14ef635e 100644
--- a/frontend/taipy-gui/package-lock.json
+++ b/frontend/taipy-gui/package-lock.json
@@ -672,9 +672,9 @@
       "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
     },
     "node_modules/@emotion/is-prop-valid": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
-      "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+      "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
       "dependencies": {
         "@emotion/memoize": "^0.9.0"
       }
@@ -708,14 +708,14 @@
       }
     },
     "node_modules/@emotion/serialize": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.1.tgz",
-      "integrity": "sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
+      "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
       "dependencies": {
         "@emotion/hash": "^0.9.2",
         "@emotion/memoize": "^0.9.0",
         "@emotion/unitless": "^0.10.0",
-        "@emotion/utils": "^1.4.0",
+        "@emotion/utils": "^1.4.1",
         "csstype": "^3.0.2"
       }
     },
@@ -760,9 +760,9 @@
       }
     },
     "node_modules/@emotion/utils": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz",
-      "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ=="
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
+      "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA=="
     },
     "node_modules/@emotion/weak-memoize": {
       "version": "0.4.0",
@@ -2113,13 +2113,13 @@
       }
     },
     "node_modules/@mui/x-date-pickers": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.17.0.tgz",
-      "integrity": "sha512-3mIw1uOZU/yKweZsVAo9QnwVFzLHqXgXG1TbGbDJ4AU6FhN2TCUlR9tzKHSlYdAHZ0bEWDS1/bgeGsQC7skXMA==",
+      "version": "7.18.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.18.0.tgz",
+      "integrity": "sha512-12tXIoMj9vpS8fS/bS3kWPCoVrH38vNGCxgplI0vOnUrN9rJuYJz3agLPJe1S0xciTw+9W8ZSe3soaW+owoz1Q==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@mui/utils": "^5.16.6",
-        "@mui/x-internals": "7.17.0",
+        "@mui/x-internals": "7.18.0",
         "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -2137,7 +2137,7 @@
         "@emotion/styled": "^11.8.1",
         "@mui/material": "^5.15.14 || ^6.0.0",
         "@mui/system": "^5.15.14 || ^6.0.0",
-        "date-fns": "^2.25.0 || ^3.2.0",
+        "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
         "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
         "dayjs": "^1.10.7",
         "luxon": "^3.0.2",
@@ -2207,9 +2207,9 @@
       }
     },
     "node_modules/@mui/x-internals": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.17.0.tgz",
-      "integrity": "sha512-FLlAGSJl/vsuaA/8hPGazXFppyzIzxApJJDZMoTS0geUmHd0hyooISV2ltllLmrZ/DGtHhI08m8GGnHL6/vVeg==",
+      "version": "7.18.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.18.0.tgz",
+      "integrity": "sha512-lzCHOWIR0cAIY1bGrWSprYerahbnH5C31ql/2OWCEjcngL2NAV1M6oKI2Vp4HheqzJ822c60UyWyapvyjSzY/A==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@mui/utils": "^5.16.6"
@@ -2255,13 +2255,13 @@
       }
     },
     "node_modules/@mui/x-tree-view": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.17.0.tgz",
-      "integrity": "sha512-xDpsF6b1D/rlkJBH6yb8kHbda2k6YOyxZ3HCYG3nq5yvUARhi2/gwRztUT0gwqAZ5UwzhL/i3U4/SomV+0T8HA==",
+      "version": "7.18.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.18.0.tgz",
+      "integrity": "sha512-3UJAYtBquc0SzKxEEdM68XlKOuuCl70ktZPqqI3z4wTZ0HK445XXc32t/s0VPIL94kRxWQcGPpgWFauScDwhug==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@mui/utils": "^5.16.6",
-        "@mui/x-internals": "7.17.0",
+        "@mui/x-internals": "7.18.0",
         "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -2448,44 +2448,44 @@
       }
     },
     "node_modules/@shikijs/core": {
-      "version": "1.17.7",
-      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.17.7.tgz",
-      "integrity": "sha512-ZnIDxFu/yvje3Q8owSHaEHd+bu/jdWhHAaJ17ggjXofHx5rc4bhpCSW+OjC6smUBi5s5dd023jWtZ1gzMu/yrw==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.18.0.tgz",
+      "integrity": "sha512-VK4BNVCd2leY62Nm2JjyxtRLkyrZT/tv104O81eyaCjHq4Adceq2uJVFJJAIof6lT1mBwZrEo2qT/T+grv3MQQ==",
       "dev": true,
       "dependencies": {
-        "@shikijs/engine-javascript": "1.17.7",
-        "@shikijs/engine-oniguruma": "1.17.7",
-        "@shikijs/types": "1.17.7",
+        "@shikijs/engine-javascript": "1.18.0",
+        "@shikijs/engine-oniguruma": "1.18.0",
+        "@shikijs/types": "1.18.0",
         "@shikijs/vscode-textmate": "^9.2.2",
         "@types/hast": "^3.0.4",
-        "hast-util-to-html": "^9.0.2"
+        "hast-util-to-html": "^9.0.3"
       }
     },
     "node_modules/@shikijs/engine-javascript": {
-      "version": "1.17.7",
-      "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.17.7.tgz",
-      "integrity": "sha512-wwSf7lKPsm+hiYQdX+1WfOXujtnUG6fnN4rCmExxa4vo+OTmvZ9B1eKauilvol/LHUPrQgW12G3gzem7pY5ckw==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.18.0.tgz",
+      "integrity": "sha512-qoP/aO/ATNwYAUw1YMdaip/YVEstMZEgrwhePm83Ll9OeQPuxDZd48szZR8oSQNQBT8m8UlWxZv8EA3lFuyI5A==",
       "dev": true,
       "dependencies": {
-        "@shikijs/types": "1.17.7",
+        "@shikijs/types": "1.18.0",
         "@shikijs/vscode-textmate": "^9.2.2",
         "oniguruma-to-js": "0.4.3"
       }
     },
     "node_modules/@shikijs/engine-oniguruma": {
-      "version": "1.17.7",
-      "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.17.7.tgz",
-      "integrity": "sha512-pvSYGnVeEIconU28NEzBXqSQC/GILbuNbAHwMoSfdTBrobKAsV1vq2K4cAgiaW1TJceLV9QMGGh18hi7cCzbVQ==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.18.0.tgz",
+      "integrity": "sha512-B9u0ZKI/cud+TcmF8Chyh+R4V5qQVvyDOqXC2l2a4x73PBSBc6sZ0JRAX3eqyJswqir6ktwApUUGBYePdKnMJg==",
       "dev": true,
       "dependencies": {
-        "@shikijs/types": "1.17.7",
+        "@shikijs/types": "1.18.0",
         "@shikijs/vscode-textmate": "^9.2.2"
       }
     },
     "node_modules/@shikijs/types": {
-      "version": "1.17.7",
-      "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.17.7.tgz",
-      "integrity": "sha512-+qA4UyhWLH2q4EFd+0z4K7GpERDU+c+CN2XYD3sC+zjvAr5iuwD1nToXZMt1YODshjkEGEDV86G7j66bKjqDdg==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.18.0.tgz",
+      "integrity": "sha512-O9N36UEaGGrxv1yUrN2nye7gDLG5Uq0/c1LyfmxsvzNPqlHzWo9DI0A4+fhW2y3bGKuQu/fwS7EPdKJJCowcVA==",
       "dev": true,
       "dependencies": {
         "@shikijs/vscode-textmate": "^9.2.2",
@@ -3065,9 +3065,9 @@
       "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
     },
     "node_modules/@types/lodash": {
-      "version": "4.17.7",
-      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
-      "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
+      "version": "4.17.9",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.9.tgz",
+      "integrity": "sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==",
       "dev": true
     },
     "node_modules/@types/mapbox__point-geometry": {
@@ -3099,9 +3099,9 @@
       "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
     },
     "node_modules/@types/node": {
-      "version": "20.16.5",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
-      "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
+      "version": "20.16.6",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz",
+      "integrity": "sha512-T7PpxM/6yeDE+AdlVysT62BX6/bECZOmQAgiFg5NoBd5MQheZ3tzal7f1wvzfiEcmrcJNRi2zRr2nY2zF+0uqw==",
       "dependencies": {
         "undici-types": "~6.19.2"
       }
@@ -3128,9 +3128,9 @@
       "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA=="
     },
     "node_modules/@types/react": {
-      "version": "18.3.7",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.7.tgz",
-      "integrity": "sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==",
+      "version": "18.3.8",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz",
+      "integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==",
       "dependencies": {
         "@types/prop-types": "*",
         "csstype": "^3.0.2"
@@ -3265,16 +3265,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
-      "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
+      "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.6.0",
-        "@typescript-eslint/type-utils": "8.6.0",
-        "@typescript-eslint/utils": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0",
+        "@typescript-eslint/scope-manager": "8.7.0",
+        "@typescript-eslint/type-utils": "8.7.0",
+        "@typescript-eslint/utils": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -3298,15 +3298,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
-      "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
+      "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.6.0",
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/typescript-estree": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0",
+        "@typescript-eslint/scope-manager": "8.7.0",
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/typescript-estree": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -3326,13 +3326,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
-      "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
+      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0"
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3343,13 +3343,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
-      "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
+      "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.6.0",
-        "@typescript-eslint/utils": "8.6.0",
+        "@typescript-eslint/typescript-estree": "8.7.0",
+        "@typescript-eslint/utils": "8.7.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -3367,9 +3367,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
-      "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
+      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3380,13 +3380,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
-      "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
+      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0",
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -3408,15 +3408,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
-      "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
+      "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.6.0",
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/typescript-estree": "8.6.0"
+        "@typescript-eslint/scope-manager": "8.7.0",
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/typescript-estree": "8.7.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3430,12 +3430,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
-      "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
+      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.6.0",
+        "@typescript-eslint/types": "8.7.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -4541,9 +4541,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001662",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz",
-      "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==",
+      "version": "1.0.30001663",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz",
+      "integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==",
       "funding": [
         {
           "type": "opencollective",
@@ -6115,9 +6115,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.25",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz",
-      "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g=="
+      "version": "1.5.28",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz",
+      "integrity": "sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw=="
     },
     "node_modules/element-size": {
       "version": "1.1.1",
@@ -6159,15 +6159,15 @@
       }
     },
     "node_modules/engine.io-client": {
-      "version": "6.5.4",
-      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
-      "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
+      "version": "6.6.1",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
+      "integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
       "dependencies": {
         "@socket.io/component-emitter": "~3.1.0",
         "debug": "~4.3.1",
         "engine.io-parser": "~5.2.1",
         "ws": "~8.17.1",
-        "xmlhttprequest-ssl": "~2.0.0"
+        "xmlhttprequest-ssl": "~2.1.1"
       }
     },
     "node_modules/engine.io-client/node_modules/ws": {
@@ -11708,9 +11708,9 @@
       }
     },
     "node_modules/maplibre-gl": {
-      "version": "4.7.0",
-      "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.0.tgz",
-      "integrity": "sha512-hkt7je7NxiMQE8EpCxLWP8t6tkK6SkrMe0hIBjYd4Ar/Q7BOCILxthGmGnU993Mwmkvs2mGiXnVUSOK12DeCzg==",
+      "version": "4.7.1",
+      "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz",
+      "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==",
       "dependencies": {
         "@mapbox/geojson-rewind": "^0.5.2",
         "@mapbox/jsonlint-lines-primitives": "^2.0.2",
@@ -13985,9 +13985,9 @@
       }
     },
     "node_modules/remark-rehype": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz",
-      "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==",
+      "version": "11.1.1",
+      "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz",
+      "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==",
       "dependencies": {
         "@types/hast": "^3.0.0",
         "@types/mdast": "^4.0.0",
@@ -14453,15 +14453,15 @@
       }
     },
     "node_modules/shiki": {
-      "version": "1.17.7",
-      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.17.7.tgz",
-      "integrity": "sha512-Zf6hNtWhFyF4XP5OOsXkBTEx9JFPiN0TQx4wSe+Vqeuczewgk2vT4IZhF4gka55uelm052BD5BaHavNqUNZd+A==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.18.0.tgz",
+      "integrity": "sha512-8jo7tOXr96h9PBQmOHVrltnETn1honZZY76YA79MHheGQg55jBvbm9dtU+MI5pjC5NJCFuA6rvVTLVeSW5cE4A==",
       "dev": true,
       "dependencies": {
-        "@shikijs/core": "1.17.7",
-        "@shikijs/engine-javascript": "1.17.7",
-        "@shikijs/engine-oniguruma": "1.17.7",
-        "@shikijs/types": "1.17.7",
+        "@shikijs/core": "1.18.0",
+        "@shikijs/engine-javascript": "1.18.0",
+        "@shikijs/engine-oniguruma": "1.18.0",
+        "@shikijs/types": "1.18.0",
         "@shikijs/vscode-textmate": "^9.2.2",
         "@types/hast": "^3.0.4"
       }
@@ -14539,13 +14539,13 @@
       }
     },
     "node_modules/socket.io-client": {
-      "version": "4.7.5",
-      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
-      "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz",
+      "integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==",
       "dependencies": {
         "@socket.io/component-emitter": "~3.1.0",
         "debug": "~4.3.2",
-        "engine.io-client": "~6.5.2",
+        "engine.io-client": "~6.6.1",
         "socket.io-parser": "~4.2.4"
       },
       "engines": {
@@ -15732,9 +15732,9 @@
       }
     },
     "node_modules/typedoc-plugin-markdown": {
-      "version": "4.2.7",
-      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.7.tgz",
-      "integrity": "sha512-bLsQdweSm48P9j6kGqQ3/4GCH5zu2EnURSkkxqirNc+uVFE9YK825ogDw+WbNkRHIV6eZK/1U43gT7YfglyYOg==",
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.8.tgz",
+      "integrity": "sha512-1EDsc66jaCjZtxdYy+Rl0KDU1WY/iyuCOOPaeFzcYFZ81FNXV8CmgUDOHri20WGmYnkEM5nQ+ooxj1vyuQo0Lg==",
       "dev": true,
       "engines": {
         "node": ">= 18"
@@ -16573,9 +16573,9 @@
       "dev": true
     },
     "node_modules/xmlhttprequest-ssl": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
-      "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
+      "integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
       "engines": {
         "node": ">=0.4.0"
       }
diff --git a/frontend/taipy-gui/packaging/taipy-gui.d.ts b/frontend/taipy-gui/packaging/taipy-gui.d.ts
index ca063b73d4..94dc7fb9b5 100644
--- a/frontend/taipy-gui/packaging/taipy-gui.d.ts
+++ b/frontend/taipy-gui/packaging/taipy-gui.d.ts
@@ -362,6 +362,9 @@ export interface ColumnDesc {
     lov?: string[];
     /** If true the user can enter any value besides the lov values. */
     freeLov?: boolean;
+    /** The name of the column that holds the formatted value to
+     *  show on the cells. */
+    formatFn?: string;
 }
 /**
  * A cell value type.
diff --git a/frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx b/frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx
index 17f016877c..28af369870 100644
--- a/frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx
+++ b/frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx
@@ -63,6 +63,7 @@ import {
     defaultColumns,
     OnRowClick,
     DownloadAction,
+    getFormatFn,
 } from "./tableUtils";
 import {
     useClassNames,
@@ -143,6 +144,7 @@ const Row = ({
                     tableClassName={tableClassName}
                     colDesc={columns[col]}
                     value={rows[index][col]}
+                    formattedVal={getFormatFn(rows[index], columns[col].formatFn, col)}
                     formatConfig={formatConfig}
                     rowIndex={index}
                     onValidation={!columns[col].notEditable ? onValidation : undefined}
@@ -281,7 +283,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
         e.stopPropagation();
     }, []);
 
-    const [colsOrder, columns, styles, tooltips, handleNan, filter, partialEditable] = useMemo(() => {
+    const [colsOrder, columns, styles, tooltips, formats, handleNan, filter, partialEditable] = useMemo(() => {
         let hNan = !!props.nanValue;
         if (baseColumns) {
             try {
@@ -320,13 +322,17 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                         pv.tooltips = pv.tooltips || {};
                         pv.tooltips[newCols[col].dfid] = newCols[col].tooltip as string;
                     }
+                    if (newCols[col].formatFn) {
+                        pv.formats = pv.formats || {};
+                        pv.formats[newCols[col].dfid] = newCols[col].formatFn;
+                    }
                     return pv;
                 }, {});
                 if (props.lineStyle) {
                     styTt.styles = styTt.styles || {};
                     styTt.styles[LINE_STYLE] = props.lineStyle;
                 }
-                return [colsOrder, newCols, styTt.styles, styTt.tooltips, hNan, filter, partialEditable];
+                return [colsOrder, newCols, styTt.styles, styTt.tooltips, styTt.formats, hNan, filter, partialEditable];
             } catch (e) {
                 console.info("ATable.columns: " + ((e as Error).message || e));
             }
@@ -336,6 +342,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
             {} as Record<string, ColumnDesc>,
             {} as Record<string, string>,
             {} as Record<string, string>,
+            {} as Record<string, string>,
             hNan,
             false,
             false,
@@ -416,6 +423,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                         applies,
                         styles,
                         tooltips,
+                        formats,
                         handleNan,
                         afs,
                         compare ? onCompare : undefined,
@@ -431,6 +439,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
             aggregates,
             styles,
             tooltips,
+            formats,
             updateVarName,
             updateVars,
             orderBy,
@@ -537,30 +546,31 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
     );
 
     const rowData: RowData = useMemo(
-        () => ({
-            colsOrder: colsOrder,
-            columns: columns,
-            rows: rows,
-            classes: {},
-            tableClassName: className,
-            cellProps: colsOrder.map((col) => ({
-                sx: getCellSx(columns[col].width || columns[col].widthHint, size),
-                component: "div",
-                variant: "body",
-            })),
-
-            isItemLoaded: isItemLoaded,
-            selection: selected,
-            formatConfig: formatConfig,
-            onValidation: active && onEdit ? onCellValidation : undefined,
-            onDeletion: active && (editable || partialEditable) && onDelete ? onRowDeletion : undefined,
-            onRowSelection: active && onAction ? onRowSelection : undefined,
-            onRowClick: active && onAction ? onRowClick : undefined,
-            lineStyle: props.lineStyle,
-            nanValue: props.nanValue,
-            compRows: compRows,
-            useCheckbox: useCheckbox,
-        } as RowData),
+        () =>
+            ({
+                colsOrder: colsOrder,
+                columns: columns,
+                rows: rows,
+                classes: {},
+                tableClassName: className,
+                cellProps: colsOrder.map((col) => ({
+                    sx: getCellSx(columns[col].width || columns[col].widthHint, size),
+                    component: "div",
+                    variant: "body",
+                })),
+
+                isItemLoaded: isItemLoaded,
+                selection: selected,
+                formatConfig: formatConfig,
+                onValidation: active && onEdit ? onCellValidation : undefined,
+                onDeletion: active && (editable || partialEditable) && onDelete ? onRowDeletion : undefined,
+                onRowSelection: active && onAction ? onRowSelection : undefined,
+                onRowClick: active && onAction ? onRowClick : undefined,
+                lineStyle: props.lineStyle,
+                nanValue: props.nanValue,
+                compRows: compRows,
+                useCheckbox: useCheckbox,
+            } as RowData),
         [
             rows,
             compRows,
diff --git a/frontend/taipy-gui/src/components/Taipy/Chart.tsx b/frontend/taipy-gui/src/components/Taipy/Chart.tsx
index 5a84d65f5f..f6e133bf0b 100644
--- a/frontend/taipy-gui/src/components/Taipy/Chart.tsx
+++ b/frontend/taipy-gui/src/components/Taipy/Chart.tsx
@@ -45,7 +45,7 @@ import {
 } from "../../utils/hooks";
 import { darkThemeTemplate } from "../../themes/darkThemeTemplate";
 import { Figure } from "react-plotly.js";
-import { ligthenPayload } from "../../context/wsUtils";
+import { lightenPayload } from "../../context/wsUtils";
 
 const Plot = lazy(() => import("react-plotly.js"));
 
@@ -616,7 +616,7 @@ const Chart = (props: ChartProp) => {
                 createSendActionNameAction(
                     id,
                     module,
-                    ligthenPayload({
+                    lightenPayload({
                         action: onClick,
                         lat: map ? yaxis.p2c() : undefined,
                         y: map ? undefined : transform(yaxis, "top")(evt?.clientY),
diff --git a/frontend/taipy-gui/src/components/Taipy/PaginatedTable.spec.tsx b/frontend/taipy-gui/src/components/Taipy/PaginatedTable.spec.tsx
index 0cbdb13336..d04abcfe58 100644
--- a/frontend/taipy-gui/src/components/Taipy/PaginatedTable.spec.tsx
+++ b/frontend/taipy-gui/src/components/Taipy/PaginatedTable.spec.tsx
@@ -176,6 +176,7 @@ const styledColumns = JSON.stringify({
         type: "int64",
         style: "some style function",
         tooltip: "some tooltip",
+        formatFn: "someFormat"
     },
 });
 
@@ -246,9 +247,6 @@ describe("PaginatedTable Component", () => {
                 sort: "asc",
                 start: 0,
                 aggregates: [],
-                applies: undefined,
-                styles: undefined,
-                tooltips: undefined,
                 filters: [],
             },
             type: "REQUEST_DATA_UPDATE",
@@ -269,16 +267,12 @@ describe("PaginatedTable Component", () => {
             payload: {
                 columns: ["Entity", "Daily hospital occupancy"],
                 end: 99,
-                id: undefined,
                 orderby: "Entity",
                 pagekey: "0-99-Entity,Daily hospital occupancy-Entity-asc",
                 handlenan: false,
                 sort: "asc",
                 start: 0,
                 aggregates: [],
-                applies: undefined,
-                styles: undefined,
-                tooltips: undefined,
                 filters: [],
             },
             type: "REQUEST_DATA_UPDATE",
@@ -322,9 +316,6 @@ describe("PaginatedTable Component", () => {
                 sort: "asc",
                 start: 100,
                 aggregates: [],
-                applies: undefined,
-                styles: undefined,
-                tooltips: undefined,
                 filters: [],
             },
             type: "REQUEST_DATA_UPDATE",
diff --git a/frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx b/frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx
index f3cae4885d..77486d40a2 100644
--- a/frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx
+++ b/frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx
@@ -69,6 +69,7 @@ import {
     getTooltip,
     OnRowClick,
     DownloadAction,
+    getFormatFn,
 } from "./tableUtils";
 import {
     useClassNames,
@@ -132,7 +133,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const baseColumns = useDynamicJsonProperty(props.columns, props.defaultColumns, defaultColumns);
 
-    const [colsOrder, columns, styles, tooltips, handleNan, filter, partialEditable] = useMemo(() => {
+    const [colsOrder, columns, styles, tooltips, formats, handleNan, filter, partialEditable] = useMemo(() => {
         let hNan = !!props.nanValue;
         if (baseColumns) {
             try {
@@ -164,12 +165,16 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                 const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
                     if (newCols[col].style) {
                         pv.styles = pv.styles || {};
-                        pv.styles[newCols[col].dfid] = newCols[col].style as string;
+                        pv.styles[newCols[col].dfid] = newCols[col].style;
                     }
                     hNan = hNan || !!newCols[col].nanValue;
                     if (newCols[col].tooltip) {
                         pv.tooltips = pv.tooltips || {};
-                        pv.tooltips[newCols[col].dfid] = newCols[col].tooltip as string;
+                        pv.tooltips[newCols[col].dfid] = newCols[col].tooltip;
+                    }
+                    if (newCols[col].formatFn) {
+                        pv.formats = pv.formats || {};
+                        pv.formats[newCols[col].dfid] = newCols[col].formatFn;
                     }
                     return pv;
                 }, {});
@@ -177,7 +182,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                     styTt.styles = styTt.styles || {};
                     styTt.styles[LINE_STYLE] = props.lineStyle;
                 }
-                return [colsOrder, newCols, styTt.styles, styTt.tooltips, hNan, filter, partialEditable];
+                return [colsOrder, newCols, styTt.styles, styTt.tooltips, styTt.formats, hNan, filter, partialEditable];
             } catch (e) {
                 console.info("PaginatedTable.columns: ", (e as Error).message || e);
             }
@@ -187,6 +192,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
             {} as Record<string, ColumnDesc>,
             {} as Record<string, string>,
             {} as Record<string, string>,
+            {} as Record<string, string>,
             hNan,
             false,
             false,
@@ -267,6 +273,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                     applies,
                     styles,
                     tooltips,
+                    formats,
                     handleNan,
                     afs,
                     compare ? onCompare : undefined,
@@ -288,6 +295,9 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
         colsOrder,
         columns,
         showAll,
+        styles,
+        tooltips,
+        formats,
         rowsPerPage,
         order,
         orderBy,
@@ -592,6 +602,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                                                     tableClassName={className}
                                                     colDesc={columns[col]}
                                                     value={row[col]}
+                                                    formattedVal={getFormatFn(row, columns[col].formatFn, col)}
                                                     formatConfig={formatConfig}
                                                     rowIndex={index}
                                                     onValidation={
diff --git a/frontend/taipy-gui/src/components/Taipy/tableUtils.tsx b/frontend/taipy-gui/src/components/Taipy/tableUtils.tsx
index e20279cea1..b5ce019750 100644
--- a/frontend/taipy-gui/src/components/Taipy/tableUtils.tsx
+++ b/frontend/taipy-gui/src/components/Taipy/tableUtils.tsx
@@ -83,6 +83,9 @@ export interface ColumnDesc {
     lov?: string[];
     /** If true the user can enter any value besides the lov values. */
     freeLov?: boolean;
+    /** The name of the column that holds the formatted value to
+     *  show on the cells. */
+    formatFn?: string;
 }
 
 export const DEFAULT_SIZE = "small";
@@ -195,6 +198,7 @@ interface EditableCellProps {
     tableCellProps?: Partial<TableCellProps>;
     comp?: RowValue;
     useCheckbox?: boolean;
+    formattedVal?: string;
 }
 
 export const defaultColumns = {} as Record<string, ColumnDesc>;
@@ -263,10 +267,16 @@ export const addDeleteColumn = (nbToRender: number, columns: Record<string, Colu
 };
 
 export const getClassName = (row: Record<string, unknown>, style?: string, col?: string) =>
-    style ? (((col && row[`tps__${col}__${style}`]) || row[style]) as string) : undefined;
+    getToolFn("tps", row, style, col);
 
 export const getTooltip = (row: Record<string, unknown>, tooltip?: string, col?: string) =>
-    tooltip ? (((col && row[`tpt__${col}__${tooltip}`]) || row[tooltip]) as string) : undefined;
+    getToolFn("tpt", row, tooltip, col);
+
+export const getFormatFn = (row: Record<string, unknown>, formatFn?: string, col?: string) =>
+    getToolFn("tpf", row, formatFn, col);
+
+const getToolFn = (prefix: string, row: Record<string, unknown>, toolFn?: string, col?: string) =>
+    toolFn ? (((col && row[`${prefix}__${col}__${toolFn}`]) || row[toolFn]) as string) : undefined;
 
 const setInputFocus = (input: HTMLInputElement) => input && input.focus();
 
@@ -296,6 +306,7 @@ export const EditableCell = (props: EditableCellProps) => {
         tableCellProps = emptyObject,
         comp,
         useCheckbox = false,
+        formattedVal: formattedValue,
     } = props;
     const [val, setVal] = useState<RowValue | Date>(value);
     const [edit, setEdit] = useState(false);
@@ -309,7 +320,7 @@ export const EditableCell = (props: EditableCellProps) => {
     const onBoolChange = useCallback((e: ChangeEvent<HTMLInputElement>) => setVal(e.target.checked), []);
     const onDateChange = useCallback((date: Date | null) => setVal(date), []);
 
-    const boolVal = colDesc.type?.startsWith("bool") && val as boolean;
+    const boolVal = colDesc.type?.startsWith("bool") && (val as boolean);
 
     const withTime = useMemo(() => !!colDesc.format && colDesc.format.toLowerCase().includes("h"), [colDesc.format]);
 
@@ -456,13 +467,12 @@ export const EditableCell = (props: EditableCellProps) => {
     );
 
     const boolTitle = useMemo(() => {
-        if (!colDesc.type?.startsWith("bool") || !colDesc.lov  || colDesc.lov.length == 0) {
-            return boolVal ? "True": "False";
+        if (!colDesc.type?.startsWith("bool") || !colDesc.lov || colDesc.lov.length == 0) {
+            return boolVal ? "True" : "False";
         }
-        return colDesc.lov[boolVal ? 1: 0];
+        return colDesc.lov[boolVal ? 1 : 0];
     }, [colDesc.type, boolVal, colDesc.lov]);
 
-
     useEffect(() => {
         !onValidation && setEdit(false);
     }, [onValidation]);
@@ -674,7 +684,12 @@ export const EditableCell = (props: EditableCellProps) => {
                             )
                         ) : (
                             <span style={defaultCursor}>
-                                {formatValue(value as RowValue, colDesc, formatConfig, nanValue)}
+                                {formatValue(
+                                    formattedValue == undefined ? value : (formattedValue as RowValue),
+                                    colDesc,
+                                    formatConfig,
+                                    nanValue
+                                )}
                             </span>
                         )}
                         {onValidation && !buttonImg ? (
diff --git a/frontend/taipy-gui/src/context/taipyReducers.ts b/frontend/taipy-gui/src/context/taipyReducers.ts
index 2320fd11bc..a6e7abb037 100644
--- a/frontend/taipy-gui/src/context/taipyReducers.ts
+++ b/frontend/taipy-gui/src/context/taipyReducers.ts
@@ -23,7 +23,7 @@ import { getBaseURL, TIMEZONE_CLIENT } from "../utils";
 import { parseData } from "../utils/dataFormat";
 import { MenuProps } from "../utils/lov";
 import { changeFavicon, getLocalStorageValue, IdMessage, storeClientId } from "./utils";
-import { ligthenPayload, sendWsMessage, TAIPY_CLIENT_ID, WsMessage } from "./wsUtils";
+import { lightenPayload, sendWsMessage, TAIPY_CLIENT_ID, WsMessage } from "./wsUtils";
 
 export enum Types {
     SocketConnected = "SOCKET_CONNECTED",
@@ -594,6 +594,7 @@ export const createRequestTableUpdateAction = (
     applies?: Record<string, unknown>,
     styles?: Record<string, string>,
     tooltips?: Record<string, string>,
+    formats?: Record<string, string>,
     handleNan?: boolean,
     filters?: Array<FilterDesc>,
     compare?: string,
@@ -606,18 +607,19 @@ export const createRequestTableUpdateAction = (
         context,
         columns,
         pageKey,
-        ligthenPayload({
-            start: start,
-            end: end,
+        lightenPayload({
+            start,
+            end,
             orderby: orderBy,
-            sort: sort,
-            aggregates: aggregates,
-            applies: applies,
-            styles: styles,
-            tooltips: tooltips,
+            sort,
+            aggregates,
+            applies,
+            styles,
+            tooltips,
+            formats,
             handlenan: handleNan,
-            filters: filters,
-            compare: compare,
+            filters,
+            compare,
             compare_datas: compareDatas,
             state_context: stateContext,
         }),
@@ -637,6 +639,7 @@ export const createRequestInfiniteTableUpdateAction = (
     applies?: Record<string, unknown>,
     styles?: Record<string, string>,
     tooltips?: Record<string, string>,
+    formats?: Record<string, string>,
     handleNan?: boolean,
     filters?: Array<FilterDesc>,
     compare?: string,
@@ -650,19 +653,20 @@ export const createRequestInfiniteTableUpdateAction = (
         context,
         columns,
         pageKey,
-        ligthenPayload({
+        lightenPayload({
             infinite: true,
-            start: start,
-            end: end,
+            start,
+            end,
             orderby: orderBy,
-            sort: sort,
-            aggregates: aggregates,
-            applies: applies,
-            styles: styles,
-            tooltips: tooltips,
+            sort,
+            aggregates,
+            applies,
+            styles,
+            tooltips,
+            formats,
             handlenan: handleNan,
-            filters: filters,
-            compare: compare,
+            filters,
+            compare,
             compare_datas: compareDatas,
             state_context: stateContext,
             reverse: !!reverse,
@@ -740,7 +744,7 @@ export const createRequestUpdateAction = (
     type: Types.RequestUpdate,
     name: "",
     context: context,
-    payload: ligthenPayload({
+    payload: lightenPayload({
         id: id,
         names: names,
         refresh: forceRefresh,
diff --git a/frontend/taipy-gui/src/context/wsUtils.ts b/frontend/taipy-gui/src/context/wsUtils.ts
index 82a99b1c9c..51cdea670d 100644
--- a/frontend/taipy-gui/src/context/wsUtils.ts
+++ b/frontend/taipy-gui/src/context/wsUtils.ts
@@ -53,11 +53,11 @@ export const sendWsMessage = (
         ack_id: ackId,
         module_context: moduleContext,
     };
-    socket?.emit("message", ligthenPayload(msg as unknown as Record<string, unknown>), serverAck);
+    socket?.emit("message", lightenPayload(msg as unknown as Record<string, unknown>), serverAck);
     return ackId;
 };
 
-export const ligthenPayload = (payload: Record<string, unknown>) => {
+export const lightenPayload = (payload: Record<string, unknown>) => {
     return Object.keys(payload || {}).reduce((pv, key) => {
         if (payload[key] !== undefined) {
             pv[key] = payload[key];
diff --git a/frontend/taipy/package-lock.json b/frontend/taipy/package-lock.json
index a38a3cad5c..f7521bd005 100644
--- a/frontend/taipy/package-lock.json
+++ b/frontend/taipy/package-lock.json
@@ -20,7 +20,8 @@
         "fast-deep-equal": "^3.1.3",
         "formik": "^2.2.9",
         "react": "^18.2.0",
-        "react-dom": "^18.2.0"
+        "react-dom": "^18.2.0",
+        "taipy-gui": "file:../../taipy/gui/webapp"
       },
       "devDependencies": {
         "@types/react": "^18.0.15",
@@ -39,6 +40,9 @@
         "webpack-cli": "^5.0.1"
       }
     },
+    "../../taipy/gui/webapp": {
+      "version": "4.0.0"
+    },
     "node_modules/@babel/code-frame": {
       "version": "7.24.7",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
@@ -220,9 +224,9 @@
       "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
     },
     "node_modules/@emotion/is-prop-valid": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
-      "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+      "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
       "dependencies": {
         "@emotion/memoize": "^0.9.0"
       }
@@ -256,14 +260,14 @@
       }
     },
     "node_modules/@emotion/serialize": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.1.tgz",
-      "integrity": "sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
+      "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
       "dependencies": {
         "@emotion/hash": "^0.9.2",
         "@emotion/memoize": "^0.9.0",
         "@emotion/unitless": "^0.10.0",
-        "@emotion/utils": "^1.4.0",
+        "@emotion/utils": "^1.4.1",
         "csstype": "^3.0.2"
       }
     },
@@ -308,9 +312,9 @@
       }
     },
     "node_modules/@emotion/utils": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz",
-      "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ=="
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
+      "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA=="
     },
     "node_modules/@emotion/weak-memoize": {
       "version": "0.4.0",
@@ -881,13 +885,13 @@
       }
     },
     "node_modules/@mui/x-date-pickers": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.17.0.tgz",
-      "integrity": "sha512-3mIw1uOZU/yKweZsVAo9QnwVFzLHqXgXG1TbGbDJ4AU6FhN2TCUlR9tzKHSlYdAHZ0bEWDS1/bgeGsQC7skXMA==",
+      "version": "7.18.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.18.0.tgz",
+      "integrity": "sha512-12tXIoMj9vpS8fS/bS3kWPCoVrH38vNGCxgplI0vOnUrN9rJuYJz3agLPJe1S0xciTw+9W8ZSe3soaW+owoz1Q==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@mui/utils": "^5.16.6",
-        "@mui/x-internals": "7.17.0",
+        "@mui/x-internals": "7.18.0",
         "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -905,7 +909,7 @@
         "@emotion/styled": "^11.8.1",
         "@mui/material": "^5.15.14 || ^6.0.0",
         "@mui/system": "^5.15.14 || ^6.0.0",
-        "date-fns": "^2.25.0 || ^3.2.0",
+        "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
         "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
         "dayjs": "^1.10.7",
         "luxon": "^3.0.2",
@@ -975,9 +979,9 @@
       }
     },
     "node_modules/@mui/x-internals": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.17.0.tgz",
-      "integrity": "sha512-FLlAGSJl/vsuaA/8hPGazXFppyzIzxApJJDZMoTS0geUmHd0hyooISV2ltllLmrZ/DGtHhI08m8GGnHL6/vVeg==",
+      "version": "7.18.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.18.0.tgz",
+      "integrity": "sha512-lzCHOWIR0cAIY1bGrWSprYerahbnH5C31ql/2OWCEjcngL2NAV1M6oKI2Vp4HheqzJ822c60UyWyapvyjSzY/A==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@mui/utils": "^5.16.6"
@@ -1023,13 +1027,13 @@
       }
     },
     "node_modules/@mui/x-tree-view": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.17.0.tgz",
-      "integrity": "sha512-xDpsF6b1D/rlkJBH6yb8kHbda2k6YOyxZ3HCYG3nq5yvUARhi2/gwRztUT0gwqAZ5UwzhL/i3U4/SomV+0T8HA==",
+      "version": "7.18.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.18.0.tgz",
+      "integrity": "sha512-3UJAYtBquc0SzKxEEdM68XlKOuuCl70ktZPqqI3z4wTZ0HK445XXc32t/s0VPIL94kRxWQcGPpgWFauScDwhug==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@mui/utils": "^5.16.6",
-        "@mui/x-internals": "7.17.0",
+        "@mui/x-internals": "7.18.0",
         "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -1268,9 +1272,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "22.5.5",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz",
-      "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==",
+      "version": "22.6.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz",
+      "integrity": "sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==",
       "dev": true,
       "dependencies": {
         "undici-types": "~6.19.2"
@@ -1287,9 +1291,9 @@
       "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA=="
     },
     "node_modules/@types/react": {
-      "version": "18.3.7",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.7.tgz",
-      "integrity": "sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==",
+      "version": "18.3.8",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz",
+      "integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==",
       "dependencies": {
         "@types/prop-types": "*",
         "csstype": "^3.0.2"
@@ -1319,16 +1323,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
-      "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
+      "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.6.0",
-        "@typescript-eslint/type-utils": "8.6.0",
-        "@typescript-eslint/utils": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0",
+        "@typescript-eslint/scope-manager": "8.7.0",
+        "@typescript-eslint/type-utils": "8.7.0",
+        "@typescript-eslint/utils": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -1352,15 +1356,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
-      "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
+      "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.6.0",
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/typescript-estree": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0",
+        "@typescript-eslint/scope-manager": "8.7.0",
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/typescript-estree": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1380,13 +1384,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
-      "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
+      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0"
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1397,13 +1401,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
-      "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
+      "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.6.0",
-        "@typescript-eslint/utils": "8.6.0",
+        "@typescript-eslint/typescript-estree": "8.7.0",
+        "@typescript-eslint/utils": "8.7.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -1421,9 +1425,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
-      "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
+      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1434,13 +1438,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
-      "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
+      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/visitor-keys": "8.6.0",
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/visitor-keys": "8.7.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -1462,15 +1466,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
-      "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
+      "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.6.0",
-        "@typescript-eslint/types": "8.6.0",
-        "@typescript-eslint/typescript-estree": "8.6.0"
+        "@typescript-eslint/scope-manager": "8.7.0",
+        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/typescript-estree": "8.7.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1484,12 +1488,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
-      "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
+      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.6.0",
+        "@typescript-eslint/types": "8.7.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -2080,9 +2084,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001662",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz",
-      "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==",
+      "version": "1.0.30001663",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz",
+      "integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==",
       "dev": true,
       "funding": [
         {
@@ -2409,9 +2413,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.25",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz",
-      "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==",
+      "version": "1.5.28",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz",
+      "integrity": "sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {
@@ -5358,6 +5362,10 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/taipy-gui": {
+      "resolved": "../../taipy/gui/webapp",
+      "link": true
+    },
     "node_modules/tapable": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
diff --git a/taipy/config/config.pyi b/taipy/config/config.pyi
index b3120d28a9..e64f6ce1d6 100644
--- a/taipy/config/config.pyi
+++ b/taipy/config/config.pyi
@@ -38,10 +38,10 @@ class Config:
 
         Here is a non-exhaustive list of configuration method examples:
 
-        - `configure_data_node()^`: Configure a data node adding and returning a `DataNodeConfig^`.
-        - `configure_task()^`: Configure a task adding and returning a `TaskConfig^`.
-        - `configure_scenario()^`: Configure a scenario adding and returning a `ScenarioConfig^`.
-        - `load()^`: Load a TOML configuration file. (This overrides the Python configuration)
+        - `configure_data_node()`: Configure a data node adding and returning a `DataNodeConfig^`.
+        - `configure_task()`: Configure a task adding and returning a `TaskConfig^`.
+        - `configure_scenario()`: Configure a scenario adding and returning a `ScenarioConfig^`.
+        - `load()`: Load a TOML configuration file. (This overrides the Python configuration)
         - more...
 
         !!! example "Most frequently used configuration methods"
diff --git a/taipy/gui/data/data_accessor.py b/taipy/gui/data/data_accessor.py
index cd8b50ba68..d20b5ea8d3 100644
--- a/taipy/gui/data/data_accessor.py
+++ b/taipy/gui/data/data_accessor.py
@@ -47,19 +47,21 @@ def to_pandas(self, value: t.Any) -> t.Union[t.List[t.Any], t.Any]:
         pass
 
     @abstractmethod
-    def on_edit(self, value: t.Any, payload: t.Dict[str, t.Any]):
+    def on_edit(self, value: t.Any, payload: t.Dict[str, t.Any]) -> t.Optional[t.Any]:
         pass
 
     @abstractmethod
-    def on_delete(self, value: t.Any, payload: t.Dict[str, t.Any]):
+    def on_delete(self, value: t.Any, payload: t.Dict[str, t.Any]) -> t.Optional[t.Any]:
         pass
 
     @abstractmethod
-    def on_add(self, value: t.Any, payload: t.Dict[str, t.Any], new_row: t.Optional[t.List[t.Any]] = None):
+    def on_add(
+        self, value: t.Any, payload: t.Dict[str, t.Any], new_row: t.Optional[t.List[t.Any]] = None
+    ) -> t.Optional[t.Any]:
         pass
 
     @abstractmethod
-    def to_csv(self, var_name: str, value: t.Any):
+    def to_csv(self, var_name: str, value: t.Any) -> t.Optional[str]:
         pass
 
 
diff --git a/taipy/gui/data/pandas_data_accessor.py b/taipy/gui/data/pandas_data_accessor.py
index e670ed31ce..dd1fabcf43 100644
--- a/taipy/gui/data/pandas_data_accessor.py
+++ b/taipy/gui/data/pandas_data_accessor.py
@@ -87,6 +87,7 @@ def __build_transferred_cols(
         is_copied: t.Optional[bool] = False,
         new_indexes: t.Optional[np.ndarray] = None,
         handle_nan: t.Optional[bool] = False,
+        formats: t.Optional[t.Dict[str, str]] = None,
     ) -> pd.DataFrame:
         dataframe = dataframe.iloc[new_indexes] if new_indexes is not None else dataframe
         if isinstance(payload_cols, list) and len(payload_cols):
@@ -98,6 +99,7 @@ def __build_transferred_cols(
         if styles:
             for k, v in styles.items():
                 col_applied = ""
+                new_data = None
                 func = self._gui._get_user_function(v)
                 if callable(func):
                     col_applied, new_data = self.__apply_user_function(
@@ -106,7 +108,6 @@ def __build_transferred_cols(
                 new_cols[col_applied or v] = new_data if col_applied else v
         if tooltips:
             for k, v in tooltips.items():
-                col_applied = ""
                 func = self._gui._get_user_function(v)
                 if callable(func):
                     col_applied, new_data = self.__apply_user_function(
@@ -114,19 +115,28 @@ def __build_transferred_cols(
                     )
                     if col_applied:
                         new_cols[col_applied] = new_data
+        if formats:
+            for k, v in formats.items():
+                func = self._gui._get_user_function(v)
+                if callable(func):
+                    col_applied, new_data = self.__apply_user_function(
+                        func, k if k in cols else None, v, dataframe, "tpf__"
+                    )
+                    if col_applied:
+                        new_cols[col_applied] = new_data
         # deal with dates
-        datecols = col_types[col_types.astype(str).str.startswith("datetime")].index.tolist()
-        if len(datecols) != 0:
+        date_cols = col_types[col_types.astype(str).str.startswith("datetime")].index.tolist()
+        if len(date_cols) != 0:
             if not is_copied:
                 # copy the df so that we don't "mess" with the user's data
                 dataframe = dataframe.copy()
             tz = Gui._get_timezone()
-            for col in datecols:
-                newcol = _get_date_col_str_name(cols, col)
+            for col in date_cols:
+                new_col = _get_date_col_str_name(cols, col)
                 re_type = _RE_PD_TYPE.match(str(col_types[col]))
-                grps = re_type.groups() if re_type else ()
-                if len(grps) > 4 and grps[4]:
-                    new_cols[newcol] = (
+                groups = re_type.groups() if re_type else ()
+                if len(groups) > 4 and groups[4]:
+                    new_cols[new_col] = (
                         dataframe[col]
                         .dt.tz_convert("UTC")
                         .dt.strftime(_DataAccessor._WS_DATE_FORMAT)
@@ -134,7 +144,7 @@ def __build_transferred_cols(
                         .replace("nan", "NaT" if handle_nan else None)
                     )
                 else:
-                    new_cols[newcol] = (
+                    new_cols[new_col] = (
                         dataframe[col]
                         .dt.tz_localize(tz)
                         .dt.tz_convert("UTC")
@@ -144,7 +154,7 @@ def __build_transferred_cols(
                     )
 
             # remove the date columns from the list of columns
-            cols = list(set(cols) - set(datecols))
+            cols = list(set(cols) - set(date_cols))
         if new_cols:
             dataframe = dataframe.assign(**new_cols)
         cols += list(new_cols.keys())
@@ -196,11 +206,11 @@ def __format_data(
             if not _has_arrow_module:
                 raise RuntimeError("Cannot use Arrow as pyarrow package is not installed")
             # Convert from pandas to Arrow
-            table = pa.Table.from_pandas(data)
+            table = pa.Table.from_pandas(data)  # type: ignore[reportPossiblyUnboundVariable]
             # Create sink buffer stream
-            sink = pa.BufferOutputStream()
+            sink = pa.BufferOutputStream()  # type: ignore[reportPossiblyUnboundVariable]
             # Create Stream writer
-            writer = pa.ipc.new_stream(sink, table.schema)
+            writer = pa.ipc.new_stream(sink, table.schema)  # type: ignore[reportPossiblyUnboundVariable]
             # Write data to table
             writer.write_table(table)
             writer.close()
@@ -273,7 +283,7 @@ def __get_data(  # noqa: C901
             except Exception as e:
                 _warn(f"Dataframe filtering: invalid query '{query}' on {df.head()}", e)
 
-        dictret: t.Optional[t.Dict[str, t.Any]]
+        dict_ret: t.Optional[t.Dict[str, t.Any]]
         if paged:
             aggregates = payload.get("aggregates")
             applies = payload.get("applies")
@@ -345,10 +355,11 @@ def __get_data(  # noqa: C901
                 styles=payload.get("styles"),
                 tooltips=payload.get("tooltips"),
                 is_copied=is_copied,
-                new_indexes=new_indexes,
+                new_indexes=t.cast(np.ndarray, new_indexes),
                 handle_nan=payload.get("handlenan", False),
+                formats=payload.get("formats"),
             )
-            dictret = self.__format_data(
+            dict_ret = self.__format_data(
                 df,
                 data_format,
                 "records",
@@ -369,8 +380,10 @@ def __get_data(  # noqa: C901
                             comp_df = t.cast(pd.DataFrame, comp_df.get(cols))
                             comp_df.columns = t.cast(pd.Index, [t.cast(tuple, c)[0] for c in cols])
                         comp_df.dropna(axis=1, how="all", inplace=True)
-                        comp_df = self.__build_transferred_cols(columns, comp_df, new_indexes=new_indexes)
-                        dictret["comp"] = self.__format_data(comp_df, data_format, "records").get("data")
+                        comp_df = self.__build_transferred_cols(
+                            columns, comp_df, new_indexes=t.cast(np.ndarray, new_indexes)
+                        )
+                        dict_ret["comp"] = self.__format_data(comp_df, data_format, "records").get("data")
                     except Exception as e:
                         _warn("Pandas accessor compare raised an exception", e)
 
@@ -418,14 +431,22 @@ def __get_data(  # noqa: C901
                             self._gui._call_on_change(f"{var_name}.{decimator}.nb_rows", len(df))
                         except Exception as e:
                             _warn(f"Limit rows error with {decimator} for Dataframe", e)
-            df = self.__build_transferred_cols(columns, t.cast(pd.DataFrame, df), is_copied=is_copied)
+            df = self.__build_transferred_cols(
+                columns,
+                t.cast(pd.DataFrame, df),
+                styles=payload.get("styles"),
+                tooltips=payload.get("tooltips"),
+                is_copied=is_copied,
+                handle_nan=payload.get("handlenan", False),
+                formats=payload.get("formats"),
+            )
             if data_format is _DataFormat.CSV:
                 ret_payload["df"] = df
-                dictret = None
+                dict_ret = None
             else:
-                dictret = self.__format_data(df, data_format, "list", data_extraction=True)
+                dict_ret = self.__format_data(df, data_format, "list", data_extraction=True)
 
-        ret_payload["value"] = dictret
+        ret_payload["value"] = dict_ret
         return ret_payload
 
     def get_data(
diff --git a/taipy/gui/utils/table_col_builder.py b/taipy/gui/utils/table_col_builder.py
index 4caf3ce7a0..a204a420fb 100644
--- a/taipy/gui/utils/table_col_builder.py
+++ b/taipy/gui/utils/table_col_builder.py
@@ -112,6 +112,21 @@ def _enhance_columns(  # noqa: C901
                 col_desc["tooltip"] = value
         else:
             _warn(f"{elt_name}: tooltip[{k}] is not in the list of displayed columns.")
+    formats = _get_name_indexed_property(attributes, "format_fn")
+    for k, v in formats.items():  # pragma: no cover
+        if col_desc := _get_column_desc(columns, k):
+            if callable(v):
+                value = hash_names.get(f"format_fn[{k}]")
+            elif isinstance(v, str):
+                value = v.strip()
+            else:
+                value = None
+            if value in columns.keys():
+                _warn(f"{elt_name}: format_fn[{k}]={value} cannot be a column's name.")
+            elif value:
+                col_desc["formatFn"] = value
+        else:
+            _warn(f"{elt_name}: format_fn[{k}] is not in the list of displayed columns.")
     editable = attributes.get("editable", False)
     loveable = _is_boolean(editable) and _is_true(editable)
     loves = _get_name_indexed_property(attributes, "lov")
diff --git a/taipy/gui/viselements.json b/taipy/gui/viselements.json
index ca7bca260b..a4f5032759 100644
--- a/taipy/gui/viselements.json
+++ b/taipy/gui/viselements.json
@@ -801,6 +801,11 @@
                         "type": "str",
                         "doc": "The name of the function that must return a tooltip text for a cell.<br/>See <a href=\"#cell-tooltips\">below</a> for details."
                     },
+                    {
+                        "name": "format_fn[<i>column_name</i>]",
+                        "type": "str",
+                        "doc": "TODO: The name of the function that must return a formatted value for a cell.<br/>See <a href=\"#cell-formats\">below</a> for details."
+                    },
                     {
                         "name": "width",
                         "type": "str",
diff --git a/tests/gui/data/test_pandas_data_accessor.py b/tests/gui/data/test_pandas_data_accessor.py
index bb47d9f6ab..7b45b56554 100644
--- a/tests/gui/data/test_pandas_data_accessor.py
+++ b/tests/gui/data/test_pandas_data_accessor.py
@@ -107,6 +107,25 @@ def tt(state, value, index: int, row, column_name: str):
         assert "tt" in data[0]
 
 
+def test_format_fn(gui: Gui, helpers, small_dataframe):
+    def ff(state, value, index: int, row, column_name: str):
+        return f"{column_name}[{index}]: {value}"
+
+    accessor = _PandasDataAccessor(gui)
+    pd = pandas.DataFrame(data=small_dataframe)
+    gui.run(run_server=False)
+    cid = helpers.create_scope_and_get_sid(gui)
+    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+        gui._bind_var_val("ff", ff)
+        gui._get_locals_bind_from_context(None)["ff"] = ff
+        g.client_id = cid
+        value = accessor.get_data("x", pd, {"start": 0, "end": 1, "formats": {"ff": "ff"}}, _DataFormat.JSON)["value"]
+        assert value["rowcount"] == 3
+        data = value["data"]
+        assert len(data) == 2
+        assert "ff" in data[0]
+
+
 def test_sort(gui: Gui, helpers, small_dataframe):
     accessor = _PandasDataAccessor(gui)
     pd = pandas.DataFrame(data=small_dataframe)


Code review comments:
diff_hunk: @@ -112,6 +112,21 @@ def _enhance_columns(  # noqa: C901
                 col_desc["tooltip"] = value
         else:
             _warn(f"{elt_name}: tooltip[{k}] is not in the list of displayed columns.")
+    formats = _get_name_indexed_property(attributes, "format_fn")
+    for k, v in formats.items():  # pragma: no cover
+        if col_desc := _get_column_desc(columns, k):
+            if callable(v):
+                value = hash_names.get(f"format_fn[{k}]")
+            elif isinstance(v, str):
+                value = v.strip()
+            else:
+                value = None
+            if value in columns.keys():
+                _warn(f"{elt_name}: format_fn[{k}]={value} cannot be a column's name.")
path: taipy/gui/utils/table_col_builder.py
text: @user1:
Not clear who 'cannot be a column's name'...

@author:
that's the value
I'll be happy to take a better wording


@author:
changed to `f"{elt_name}: format_fn[{k}] cannot reference a column's name '{value}'."`