HowTo: Importing Nextcloud Tables/View Data into Excel and Power BI

Recently, I struggled to import data from NC Tables to Microsoft Excel or Power BI and couldn’t find the right documentation. Now that I have figured it out, I would like to share the solution with the community so that you don’t have to conduct in-depth research again. :grinning_face:


1. Prerequisites

Authentication

  • If your Nextcloud instance requires app passwords for external authentication, ensure you have one created.

  • If your Nextcloud instance uses standard username/password authentication, you can directly use your credentials in Excel or Power BI.


2. Importing Data into Excel

Step 1: Open Excel and Get Data

  1. Open Excel.

  2. Go to Data → Get Data → From Other Sources → From Web.

Step 2: Enter API URL

  • Enter the following URL (replace {tableId} with your Nextcloud Tables table ID and {your-nextcloud-domain} with your domain):

    https://{your-nextcloud-domain}/apps/tables/api/1/tables/{tableId}/rows
    
  • Click OK.

Step 3: Set Up Authentication

  1. Select Basic authentication.

  2. Enter your Nextcloud username.

  3. Enter your password (or app password if required).

  4. Click Connect.

Step 4: Open Power Query Editor

  • After loading, the Power Query Editor will open.

  • Click Advanced Editor (under Home → Advanced Editor).

Step 5: Insert M Code

Replace the existing code with the following (adjust {your-nextcloud-domain} and {tableId}):

let
    // 1. Fetch data from /tables/{tableId}/rows
    DatenJson = Json.Document(Web.Contents("https://{your-nextcloud-domain}/apps/tables/api/1/tables/{tableId}/rows")),

    // 2. Fetch headers and selectionOptions from /tables/{tableId}/columns
    HeaderJson = Json.Document(Web.Contents("https://{your-nextcloud-domain}/apps/tables/api/1/tables/{tableId}/columns")),
    // Extract headers from the "title" field
    HeaderListe = List.Transform(HeaderJson, each [title]),
    // Extract columnIds from headers
    ColumnIds = List.Transform(HeaderJson, each [id]),
    // Create a dictionary for selectionOptions
    SelectionOptionsDict = List.Accumulate(
        HeaderJson,
        [],
        (state, current) =>
            if Record.HasFields(current, "selectionOptions") and List.Count(current[selectionOptions]) > 0 then
                let
                    columnIdText = Text.From(current[id]),
                    optionsList = List.Transform(current[selectionOptions], (option) => {option[id], option[label]})
                in
                    Record.AddField(state, columnIdText, optionsList)
            else
                state
    ),
    // Create a dictionary for column types
    ColumnTypesDict = List.Accumulate(
        HeaderJson,
        [],
        (state, current) =>
            let
                columnIdText = Text.From(current[id])
            in
                Record.AddField(state, columnIdText, {current[type], current[subtype]})
    ),
    // 3. Create a list of lists with the `value` fields
    ValueLists = List.Transform(DatenJson, (row) =>
        let
            // Create a record from data entries
            DataRecord = List.Accumulate(
                row[data],
                [],
                (state, current) => Record.AddField(state, Text.From(current[columnId]), current[value])
            ),

            // Fill missing values with null and process different data types
            FilledValues = List.Transform(ColumnIds, (columnId) =>
                let
                    columnIdText = Text.From(columnId),
                    itemValue = if Record.HasFields(DataRecord, columnIdText) then Record.Field(DataRecord, columnIdText) else null,
                    columnType = Record.FieldOrDefault(ColumnTypesDict, columnIdText, {null, null}),
                    columnTypeName = columnType{0},
                    columnSubtype = columnType{1},

                    // Process different data types
                    finalValue =
                        if itemValue = null then null
                        // Text with subtype "link" - parse JSON
                        else if columnTypeName = "text" and columnSubtype = "link" then
                            try
                                let
                                    linkJson = Json.Document(itemValue)
                                in
                                    linkJson[value]
                            otherwise itemValue
                        // Selection with subtype "multi" - multiple values
                        else if columnTypeName = "selection" and columnSubtype = "multi" then
                            if itemValue is list then
                                let
                                    options = Record.FieldOrDefault(SelectionOptionsDict, columnIdText, null)
                                in
                                    if options <> null then
                                        Text.Combine(
                                            List.Transform(itemValue, (id) =>
                                                let
                                                    matchingOption = List.First(List.Select(options, (option) => option{0} = id), null)
                                                in
                                                    if matchingOption <> null then matchingOption{1} else Text.From(id)
                                            ),
                                            ", "
                                        )
                                    else
                                        Text.Combine(List.Transform(itemValue, Text.From), ", ")
                            else null
                        // Selection (single) - single value
                        else if columnTypeName = "selection" and columnSubtype <> "check" then
                            if itemValue is number then
                                let
                                    options = Record.FieldOrDefault(SelectionOptionsDict, columnIdText, null)
                                in
                                    if options <> null then
                                        let
                                            matchingOption = List.First(List.Select(options, (option) => option{0} = itemValue), null)
                                        in
                                            if matchingOption <> null then matchingOption{1} else itemValue
                                    else itemValue
                            else itemValue
                        // Selection with subtype "check" - Boolean
                        else if columnTypeName = "selection" and columnSubtype = "check" then
                            if itemValue = "true" then true
                            else if itemValue = "false" then false
                            else null
                        // Usergroup - can be person/group/team
                        else if columnTypeName = "usergroup" then
                            if itemValue is list and List.Count(itemValue) > 0 then
                                Text.Combine(
                                    List.Transform(itemValue, (user) => user[displayName]),
                                    ", "
                                )
                            else null
                        // All other types
                        else itemValue
                in
                    finalValue
            )
        in
            FilledValues
    ),
    // 4. Create a table from the `value` lists and headers
    Tabelle = Table.FromRecords(
        List.Transform(ValueLists, (valueList) => Record.FromList(valueList, HeaderListe)),
        HeaderListe
    )
in
    Tabelle

Step 6: Load Data

  • Click Done in the Advanced Editor.

  • Click Close & Load to import the data into Excel.

Step 7: Refresh Data

  • To refresh the data, right-click the table and select Refresh.

3. Importing Data into Power BI

Step 1: Open Power BI and Get Data

  1. Open Power BI Desktop.

  2. Click Get Data → Web.

Step 2: Enter API URL

  • Enter the same URL as in Excel:

    https://{your-nextcloud-domain}/apps/tables/api/1/tables/{tableId}/rows
    
  • Click OK.

Step 3: Set Up Authentication

  1. Select Basic authentication.

  2. Enter your Nextcloud username.

  3. Enter your password (or app password if required).

  4. Click Connect.

Step 4: Transform Data in Power Query Editor

  • Follow Steps 4–6 from the Excel section to insert the M code and load the data.

Step 5: Model and Visualize Data

  • Use the imported data to create visualizations in Power BI.

Step 6: Refresh Data

  • To refresh the data, click Home → Refresh or set up a scheduled refresh in the Power BI Service.

4. Summary of Steps

  1. Set up authentication (app password if required)
  2. Open Excel/Power BI and select Web data source
  3. Enter API URL and authentication details
  4. Insert M code in the Advanced Editor
  5. Load and transform data
  6. Refresh data as needed

Notes

  • Replace {your-nextcloud-domain} and {tableId} with your actual values.

  • This guide works for Nextcloud Tables with both Excel and Power BI.

  • For environments with standard username/password authentication, you can directly use your credentials.

3 Likes