3

I'm building a Dash app in Python and trying to add a button that lets the user copy the table content to the clipboard so they can paste it into Excel.

The table displays correctly, but when I click the "Copy table" button, nothing is actually copied to the clipboard — even though the message “Table copied” appears.

Here's a simplified version of my code:


from dash import Dash, html, dash_table, dcc, Input, Output, State
import pandas as pd

app = Dash(__name__)

app.layout = html.Div([
    html.Button("Copy table", id="copy_btn", n_clicks=0, className="btn btn-primary"),
    dcc.Clipboard(id="clipboard_table", style={"display": "none"}),
    html.Div(id="message_copy", style={"color": "green"}),

    dash_table.DataTable(
        id="sales_table",
        columns=[
            {"name": "Department", "id": "department"},
            {"name": "Sales", "id": "sales"},
            {"name": "Weight", "id": "weight"},
        ],
        data=[
            {"department": "Phones", "sales": 1000, "weight": "55.8%"},
            {"department": "Computers", "sales": 600, "weight": "26.1%"},
        ],
        style_table={'overflowX': 'auto'},
    )
])

@app.callback(
    Output("clipboard_table", "content"),
    Output("message_copy", "children"),
    Input("copy_btn", "n_clicks"),
    State("sales_table", "data"),
    prevent_initial_call=True
)
def copy_table(n_clicks, data):
    if not data:
        return "", "⚠️ No data to copy"
    df = pd.DataFrame(data)
    text = df.to_csv(sep="\t", index=False)
    return text, "✅ Table copied. You can paste it in Excel (Ctrl+V)"

if __name__ == "__main__":
    app.run_server(debug=True)

I’ve tried:

Using dcc.Clipboard() as shown in the Dash docs.

Injecting JavaScript with html.Script() to use navigator.clipboard.writeText(text) — but that doesn't run when deployed (Render blocks inline scripts).

Is there a recommended or secure Dash-native way to do this, especially when the app is deployed (e.g., on Render or Heroku)?

2
  • in question (not in comments) you could add link to documentation. Commented Oct 21 at 1:03
  • as a user I would rather prefer link to download data as CSV file - so I could use it with other tools (like pandas) instead of Excel. Commented Oct 21 at 1:05

2 Answers 2

1

The error happens perhaps because most browsers block clipboard access unless it’s triggered by a direct user gesture (like a button click). If you try to copy text inside a regular callback or automatically after rendering, it won’t work.

You can fix this by moving the clipboard logic into a clientside callback that runs in the browser and is triggered by the user’s click event.

Here’s a minimal working example:

from dash import Dash, html, dash_table, Input, Output, State

app = Dash(__name__)

app.layout = html.Div([
    html.Button("Copy table", id="copy_btn"),
    html.Div(id="message_copy", style={"color": "green"}),
    dash_table.DataTable(
        id="sales_table",
        columns=[{"name": c, "id": c} for c in ["Department", "Sales", "Weight"]],
        data=[
            {"Department": "Phones", "Sales": 1000, "Weight": "55.8%"},
            {"Department": "Computers", "Sales": 600, "Weight": "26.1%"},
        ],
        style_table={'overflowX': 'auto'},
    )
])

app.clientside_callback(
    """
    function(n_clicks, data) {
        if (!n_clicks) return;
        const cols = Object.keys(data[0]);
        const tsv = [cols.join('\\t')]
            .concat(data.map(r => cols.map(c => r[c]).join('\\t')))
            .join('\\n');
        navigator.clipboard.writeText(tsv);
        return "Copied to clipboard! Paste in Excel.";
    }
    """,
    Output("message_copy", "children"),
    Input("copy_btn", "n_clicks"),
    State("sales_table", "data")
)

if __name__ == "__main__":
    app.run(debug=True)
  • The navigator.clipboard.writeText() call is made inside a user gesture (button click).
  • It runs client-side, so no Python round-trip delay.
  • Works on localhost or HTTPS (required by browsers for clipboard access).

If you deploy this app, make sure it’s served over HTTPS.

output (copy the table): enter image description here

pasting on the excel file:

enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

wroked great, thank you very much!
0

It seems you have to also send n_clicks to dcc.Clipboard to update content in clipboard.

You can use n_clicks from html.Button to send new value.

@app.callback(
    Output("clipboard_table", "content"),
    Output("clipboard_table", "n_clicks"),   # <--- HERE
    Output("message_copy", "children"),
    Input("copy_btn", "n_clicks"),
    State("sales_table", "data"),
    prevent_initial_call=True,
)
def copy_table(n_clicks, data):
    if not data:
        return "", "⚠️ No data to copy"
    df = pd.DataFrame(data)
    text = df.to_csv(sep="\t", index=False)
    return text, n_clicks, "✅ Table copied. You can paste it in Excel (Ctrl+V)"
#                ^ HERE ^

It works for me with Firefox, Chrome, Edge, Opera on Linux Mint.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.