diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html
index a7cceb0d8a..7743e6c630 100644
--- a/InvenTree/order/templates/order/purchase_order_detail.html
+++ b/InvenTree/order/templates/order/purchase_order_detail.html
@@ -259,19 +259,21 @@ $("#new-po-extra-line").click(function() {
     });
 });
 
-loadPurchaseOrderExtraLineTable(
-    '#po-extra-lines-table',
-    {
-        order: {{ order.pk }},
-        status: {{ order.status }},
-        {% if order.is_pending %}
-        pending: true,
-        {% endif %}
-        {% if roles.purchase_order.change %}
-        allow_edit: true,
-        {% endif %}
-    }
-);
+loadExtraLineTable({
+    table: '#po-extra-lines-table',
+    order: {{ order.pk }},
+    url: '{% url "api-po-extra-line-list" %}',
+    name: 'purchaseorderextraline',
+    filtertarget: '#filter-list-purchase-order-extra-lines',
+    {% settings_value "PURCHASEORDER_EDIT_COMPLETED_ORDERS" as allow_edit %}
+    {% if order.is_pending or allow_edit %}
+    allow_edit: {% js_bool roles.purchase_order.change %},
+    allow_delete: {% js_bool roles.purchase_order.delete %},
+    {% else %}
+    allow_edit: false,
+    allow_delete: false,
+    {% endif %}
+});
 
 loadOrderTotal(
     '#poTotalPrice',
diff --git a/InvenTree/order/templates/order/return_order_detail.html b/InvenTree/order/templates/order/return_order_detail.html
index 8cff9bbf9a..54107d6cf2 100644
--- a/InvenTree/order/templates/order/return_order_detail.html
+++ b/InvenTree/order/templates/order/return_order_detail.html
@@ -13,11 +13,42 @@
 
 <div class='panel panel-hidden' id='panel-order-details'>
     <div class='panel-heading'>
-        <h4>{% trans "Order Details" %}</h4>
-        {% include "spacer.html" %}
+        <div class='d-flex flex-wrap'>
+            <h4>{% trans "Line Items" %}</h4>
+            {% include "spacer.html" %}
+            <!-- TODO: Actions -->
+        </div>
     </div>
     <div class='panel-content'>
-        <!-- TODO: Order details here -->
+        <div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
+            <div class='btn-group'>
+                {% include "filter_list.html" with id="return-order-lines" %}
+            </div>
+        </div>
+        <table class='table table-striped table-condensed' id='return-order-lines-table' data-toolbar='#order-toolbar-buttons'>
+        </table>
+    </div>
+    <div class='panel-heading'>
+        <div class='d-flex flex-wrap'>
+            <h4>{% trans "Extra Lines" %}</h4>
+            {% include "spacer.html" %}
+            <div class='btn-group' role='group'>
+                {% if roles.return_order.change %}
+                <button type='button' class='btn btn-success' id='new-return-order-extra-line'>
+                    <span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
+                </button>
+                {% endif %}
+            </div>
+        </div>
+    </div>
+    <div class='panel-content'>
+        <div id='order-extra-toolbar-buttons' class='btn-group' style='float: right;'>
+            <div class='btn-group'>
+                {% include "filter_list.html" with id="return-order-extra-lines" %}
+            </div>
+        </div>
+        <table class='table table-striped table-condensed' id='return-order-extra-lines-table' data-toolbar='#order-extra-toolbar-buttons'>
+        </table>
     </div>
 </div>
 
@@ -59,7 +90,20 @@
 
 // Callback function when the 'details' panel is loaded
 onPanelLoad('order-details', function() {
-    // TODO
+
+    loadExtraLineTable({
+        order: {{ order.pk }},
+        url: '{% url "api-return-order-extra-line-list" %}',
+        table: "#return-order-extra-lines-table",
+        name: 'returnorderextralines',
+        filtertarget: '#filter-list-return-order-extra-lines',
+        allow_edit: {% js_bool roles.return_order.change %},
+        allow_delete: {% js_bool roles.return_order.delete %},
+    });
+
+    $('#new-return-order-extra-line').click(function() {
+        // TODO: Create new return order extra line item
+    });
 });
 
 // Callback function when the 'notes' panel is loaded
diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html
index 269a1f1397..864c112c3f 100644
--- a/InvenTree/order/templates/order/sales_order_detail.html
+++ b/InvenTree/order/templates/order/sales_order_detail.html
@@ -289,15 +289,21 @@
         });
     });
 
-    loadSalesOrderExtraLineTable(
-        '#so-extra-lines-table',
-        {
-            order: {{ order.pk }},
-            status: {{ order.status }},
-            {% if roles.sales_order.change %}allow_edit: true,{% endif %}
-            {% if order.is_pending %}pending: true,{% endif %}
-        }
-    );
+    loadExtraLineTable({
+        order: {{ order.pk }},
+        table: '#so-extra-lines-table',
+        url: '{% url "api-so-extra-line-list" %}',
+        name: 'salesorderextraline',
+        filtertarget: '#filter-list-sales-order-extra-lines',
+        {% settings_value "SALESORDER_EDIT_COMPLETED_ORDERS" as allow_edit %}
+        {% if order.is_pending or allow_edit %}
+        allow_edit: {% js_bool roles.sales_order.change %},
+        allow_delete: {% js_bool roles.sales_order.delete %},
+        {% else %}
+        allow_edit: false,
+        allow_delete: false,
+        {% endif %}
+    });
 
     loadOrderTotal(
         '#soTotalPrice',
diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js
index b1fc49639d..d255f45c25 100644
--- a/InvenTree/templates/js/translated/order.js
+++ b/InvenTree/templates/js/translated/order.js
@@ -14,6 +14,7 @@
     removeOrderRowFromOrderWizard,
     removePurchaseOrderLineItem,
     loadOrderTotal,
+    loadExtraLineTable,
     extraLineFields,
     reloadTotal,
 */
@@ -122,3 +123,197 @@ function reloadTotal() {
         }
     );
 };
+
+
+/*
+ * Load a table displaying "extra" line items for a given order.
+ * Used for all external order types (e.g. PurchaseOrder / SalesOrder / ReturnOrder)
+ *
+ * options:
+ *  - table: The DOM ID of the table element
+ *  - order: The ID of the related order (required)
+ *  - name: The unique 'name' for this table
+ *  - url: The API URL for the extra line item model (required)
+ *  - filtertarget: The DOM ID for the filter list element
+ */
+function loadExtraLineTable(options={}) {
+
+    const table = options.table;
+
+    options.params = options.params || {};
+
+    // Filtering
+    options.params.order = options.order;
+
+    var filters = {};
+
+    if (options.name) {
+        filters = loadTableFilters(options.name);
+    }
+
+    for (var key in options.params) {
+        filters[key] = options.params[key];
+    }
+
+    setupFilterList(
+        options.name,
+        $(table),
+        options.filtertarget,
+        {
+            download: true
+        }
+    );
+
+    // Helper function to reload table
+    function reloadExtraLineTable() {
+        $(table).bootstrapTable('refresh');
+        reloadTotal();
+    }
+
+    // Configure callback functions once the table is loaded
+    function setupCallbacks() {
+
+        if (options.allow_edit) {
+
+            // Callback to duplicate line item
+            $(table).find('.button-duplicate').click(function() {
+                var pk = $(this).attr('pk');
+
+                inventreeGet(`${options.url}${pk}/`, {}, {
+                    success: function(data) {
+
+                        var fields = extraLineFields();
+
+                        constructForm(options.url, {
+                            method: 'POST',
+                            fields: fields,
+                            data: data,
+                            title: '{% trans "Duplicate Line" %}',
+                            onSuccess: reloadExtraLineTable,
+                        });
+                    }
+                });
+            });
+
+            // Callback to edit line item
+            // Callback for editing lines
+            $(table).find('.button-edit').click(function() {
+                var pk = $(this).attr('pk');
+
+                constructForm(`${options.url}${pk}/`, {
+                    fields: extraLineFields(),
+                    title: '{% trans "Edit Line" %}',
+                    onSuccess: reloadExtraLineTable,
+                });
+            });
+        }
+
+        if (options.allow_delete) {
+            // Callback for deleting lines
+            $(table).find('.button-delete').click(function() {
+                var pk = $(this).attr('pk');
+
+                constructForm(`${options.url}${pk}/`, {
+                    method: 'DELETE',
+                    title: '{% trans "Delete Line" %}',
+                    onSuccess: reloadExtraLineTable,
+                });
+            });
+        }
+    }
+
+    $(table).inventreeTable({
+        url: options.url,
+        name: options.name,
+        sidePagination: 'server',
+        onPostBody: setupCallbacks,
+        formatNoMatches: function() {
+            return '{% trans "No line items found" %}';
+        },
+        queryParams: filters,
+        original: options.params,
+        showFooter: true,
+        uniqueId: 'pk',
+        columns: [
+            {
+                sortable: true,
+                field: 'reference',
+                title: '{% trans "Reference" %}',
+                switchable: false,
+            },
+            {
+                sortable: true,
+                switchable: false,
+                field: 'quantity',
+                title: '{% trans "Quantity" %}',
+                footerFormatter: function(data) {
+                    return data.map(function(row) {
+                        return +row['quantity'];
+                    }).reduce(function(sum, i) {
+                        return sum + i;
+                    }, 0);
+                },
+            },
+            {
+                sortable: true,
+                field: 'price',
+                title: '{% trans "Unit Price" %}',
+                formatter: function(value, row) {
+                    return formatCurrency(row.price, {
+                        currency: row.price_currency,
+                    });
+                }
+            },
+            {
+                field: 'total_price',
+                sortable: true,
+                switchable: true,
+                title: '{% trans "Total Price" %}',
+                formatter: function(value, row) {
+                    return formatCurrency(row.price * row.quantity, {
+                        currency: row.price_currency,
+                    });
+                },
+                footerFormatter: function(data) {
+                    return calculateTotalPrice(
+                        data,
+                        function(row) {
+                            return row.price ? row.price * row.quantity : null;
+                        },
+                        function(row) {
+                            return row.price_currency;
+                        }
+                    );
+                }
+            },
+            {
+                field: 'notes',
+                title: '{% trans "Notes" %}',
+            },
+            {
+                field: 'buttons',
+                switchable: false,
+                formatter: function(value, row, index, field) {
+
+                    var html = `<div class='btn-group float-right' role='group'>`;
+
+                    if (options.allow_edit || options.allow_delete) {
+                        var pk = row.pk;
+
+                        if (options.allow_edit) {
+                            html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
+                            html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
+                        }
+
+                        if (options.allow_delete) {
+                            html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
+                        }
+                    }
+
+                    html += `</div>`;
+                    return html;
+                }
+            },
+        ]
+    })
+}
diff --git a/InvenTree/templates/js/translated/purchase_order.js b/InvenTree/templates/js/translated/purchase_order.js
index 85df6c4a76..f3586cfb96 100644
--- a/InvenTree/templates/js/translated/purchase_order.js
+++ b/InvenTree/templates/js/translated/purchase_order.js
@@ -28,7 +28,6 @@
     editPurchaseOrderLineItem,
     issuePurchaseOrder,
     loadPurchaseOrderLineItemTable,
-    loadPurchaseOrderExtraLineTable
     loadPurchaseOrderTable,
     newPurchaseOrderFromOrderWizard,
     newSupplierPartFromOrderWizard,
@@ -2085,198 +2084,3 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
     );
 
 }
-
-
-/**
- * Load a table displaying lines for a particular PurchaseOrder
- *
- * @param {String} table : HTML ID tag e.g. '#table'
- * @param {Object} options : object which contains:
- *      - order {integer} : pk of the PurchaseOrder
- *      - status: {integer} : status code for the order
- */
-function loadPurchaseOrderExtraLineTable(table, options={}) {
-
-    options.table = table;
-
-    if (!options.pending && !global_settings.PURCHASEORDER_EDIT_COMPLETED_ORDERS) {
-        options.allow_edit = false;
-    }
-
-    options.params = options.params || {};
-
-    if (!options.order) {
-        console.error('function called without order ID');
-        return;
-    }
-
-    if (!options.status) {
-        console.error('function called without order status');
-        return;
-    }
-
-    options.params.order = options.order;
-    options.params.part_detail = true;
-    options.params.allocations = true;
-
-    var filters = loadTableFilters('purchaseorderextraline');
-
-    for (var key in options.params) {
-        filters[key] = options.params[key];
-    }
-
-    options.url = options.url || '{% url "api-po-extra-line-list" %}';
-
-    var filter_target = options.filter_target || '#filter-list-purchase-order-extra-lines';
-
-    setupFilterList('purchaseorderextraline', $(table), filter_target, {download: true});
-
-    // Table columns to display
-    var columns = [
-        {
-            sortable: true,
-            field: 'reference',
-            title: '{% trans "Reference" %}',
-            switchable: true,
-        },
-        {
-            sortable: true,
-            field: 'quantity',
-            title: '{% trans "Quantity" %}',
-            footerFormatter: function(data) {
-                return data.map(function(row) {
-                    return +row['quantity'];
-                }).reduce(function(sum, i) {
-                    return sum + i;
-                }, 0);
-            },
-            switchable: false,
-        },
-        {
-            sortable: true,
-            field: 'price',
-            title: '{% trans "Unit Price" %}',
-            formatter: function(value, row) {
-                return formatCurrency(row.price, {
-                    currency: row.price_currency,
-                });
-            }
-        },
-        {
-            field: 'total_price',
-            sortable: true,
-            title: '{% trans "Total Price" %}',
-            formatter: function(value, row) {
-                return formatCurrency(row.price * row.quantity, {
-                    currency: row.price_currency,
-                });
-            },
-            footerFormatter: function(data) {
-                return calculateTotalPrice(
-                    data,
-                    function(row) {
-                        return row.price ? row.price * row.quantity : null;
-                    },
-                    function(row) {
-                        return row.price_currency;
-                    }
-                );
-            }
-        }
-    ];
-
-    columns.push({
-        field: 'notes',
-        title: '{% trans "Notes" %}',
-    });
-
-    columns.push({
-        field: 'buttons',
-        switchable: false,
-        formatter: function(value, row, index, field) {
-
-            var html = `<div class='btn-group float-right' role='group'>`;
-
-            var pk = row.pk;
-
-            if (options.allow_edit) {
-                html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
-                html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
-                html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
-            }
-
-            html += `</div>`;
-
-            return html;
-        }
-    });
-
-    function reloadTable() {
-        $(table).bootstrapTable('refresh');
-        reloadTotal();
-    }
-
-    // Configure callback functions once the table is loaded
-    function setupCallbacks() {
-
-        // Callback for duplicating lines
-        $(table).find('.button-duplicate').click(function() {
-            var pk = $(this).attr('pk');
-
-            inventreeGet(`/api/order/po-extra-line/${pk}/`, {}, {
-                success: function(data) {
-
-                    var fields = extraLineFields();
-
-                    constructForm('{% url "api-po-extra-line-list" %}', {
-                        method: 'POST',
-                        fields: fields,
-                        data: data,
-                        title: '{% trans "Duplicate Line" %}',
-                        onSuccess: function(response) {
-                            $(table).bootstrapTable('refresh');
-                        }
-                    });
-                }
-            });
-        });
-
-        // Callback for editing lines
-        $(table).find('.button-edit').click(function() {
-            var pk = $(this).attr('pk');
-
-            constructForm(`/api/order/po-extra-line/${pk}/`, {
-                fields: extraLineFields(),
-                title: '{% trans "Edit Line" %}',
-                onSuccess: reloadTable,
-            });
-        });
-
-        // Callback for deleting lines
-        $(table).find('.button-delete').click(function() {
-            var pk = $(this).attr('pk');
-
-            constructForm(`/api/order/po-extra-line/${pk}/`, {
-                method: 'DELETE',
-                title: '{% trans "Delete Line" %}',
-                onSuccess: reloadTable,
-            });
-        });
-    }
-
-    $(table).inventreeTable({
-        onPostBody: setupCallbacks,
-        name: 'purchaseorderextraline',
-        sidePagination: 'client',
-        formatNoMatches: function() {
-            return '{% trans "No matching line" %}';
-        },
-        queryParams: filters,
-        original: options.params,
-        url: options.url,
-        showFooter: true,
-        uniqueId: 'pk',
-        detailViewByClick: false,
-        columns: columns,
-    });
-}
diff --git a/InvenTree/templates/js/translated/sales_order.js b/InvenTree/templates/js/translated/sales_order.js
index 27a39fd7a8..8bb9ba9a84 100644
--- a/InvenTree/templates/js/translated/sales_order.js
+++ b/InvenTree/templates/js/translated/sales_order.js
@@ -29,7 +29,6 @@
     exportOrder,
     loadSalesOrderAllocationTable,
     loadSalesOrderLineItemTable,
-    loadSalesOrderExtraLineTable
     loadSalesOrderShipmentTable,
     loadSalesOrderTable,
     orderParts,
@@ -2143,196 +2142,3 @@ function loadSalesOrderLineItemTable(table, options={}) {
         columns: columns,
     });
 }
-
-
-/**
- * Load a table displaying lines for a particular SalesOrder
- *
- * @param {String} table : HTML ID tag e.g. '#table'
- * @param {Object} options : object which contains:
- *      - order {integer} : pk of the SalesOrder
- *      - status: {integer} : status code for the order
- */
-function loadSalesOrderExtraLineTable(table, options={}) {
-
-    options.table = table;
-
-    if (!options.pending && !global_settings.SALESORDER_EDIT_COMPLETED_ORDERS) {
-        options.allow_edit = false;
-    }
-
-    options.params = options.params || {};
-
-    if (!options.order) {
-        console.error('function called without order ID');
-        return;
-    }
-
-    if (!options.status) {
-        console.error('function called without order status');
-        return;
-    }
-
-    options.params.order = options.order;
-    options.params.part_detail = true;
-    options.params.allocations = true;
-
-    var filters = loadTableFilters('salesorderextraline');
-
-    for (var key in options.params) {
-        filters[key] = options.params[key];
-    }
-
-    options.url = options.url || '{% url "api-so-extra-line-list" %}';
-
-    var filter_target = options.filter_target || '#filter-list-sales-order-extra-lines';
-
-    setupFilterList('salesorderextraline', $(table), filter_target, {download: true});
-
-    // Table columns to display
-    var columns = [
-        {
-            sortable: true,
-            field: 'reference',
-            title: '{% trans "Reference" %}',
-            switchable: true,
-        },
-        {
-            sortable: true,
-            field: 'quantity',
-            title: '{% trans "Quantity" %}',
-            footerFormatter: function(data) {
-                return data.map(function(row) {
-                    return +row['quantity'];
-                }).reduce(function(sum, i) {
-                    return sum + i;
-                }, 0);
-            },
-            switchable: false,
-        },
-        {
-            sortable: true,
-            field: 'price',
-            title: '{% trans "Unit Price" %}',
-            formatter: function(value, row) {
-                return formatCurrency(row.price, {
-                    currency: row.price_currency,
-                });
-            }
-        },
-        {
-            field: 'total_price',
-            sortable: true,
-            title: '{% trans "Total Price" %}',
-            formatter: function(value, row) {
-                return formatCurrency(row.price * row.quantity, {
-                    currency: row.price_currency,
-                });
-            },
-            footerFormatter: function(data) {
-                return calculateTotalPrice(
-                    data,
-                    function(row) {
-                        return row.price ? row.price * row.quantity : null;
-                    },
-                    function(row) {
-                        return row.price_currency;
-                    }
-                );
-            }
-        }
-    ];
-
-    columns.push({
-        field: 'notes',
-        title: '{% trans "Notes" %}',
-    });
-
-    columns.push({
-        field: 'buttons',
-        switchable: false,
-        formatter: function(value, row, index, field) {
-
-            var html = `<div class='btn-group float-right' role='group'>`;
-
-            if (options.allow_edit) {
-                var pk = row.pk;
-                html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
-                html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
-                html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
-            }
-
-            html += `</div>`;
-            return html;
-        }
-    });
-
-    function reloadTable() {
-        $(table).bootstrapTable('refresh');
-        reloadTotal();
-    }
-
-    // Configure callback functions once the table is loaded
-    function setupCallbacks() {
-
-        // Callback for duplicating lines
-        $(table).find('.button-duplicate').click(function() {
-            var pk = $(this).attr('pk');
-
-            inventreeGet(`/api/order/so-extra-line/${pk}/`, {}, {
-                success: function(data) {
-
-                    var fields = extraLineFields();
-
-                    constructForm('{% url "api-so-extra-line-list" %}', {
-                        method: 'POST',
-                        fields: fields,
-                        data: data,
-                        title: '{% trans "Duplicate Line" %}',
-                        onSuccess: function(response) {
-                            $(table).bootstrapTable('refresh');
-                        }
-                    });
-                }
-            });
-        });
-
-        // Callback for editing lines
-        $(table).find('.button-edit').click(function() {
-            var pk = $(this).attr('pk');
-
-            constructForm(`/api/order/so-extra-line/${pk}/`, {
-                fields: extraLineFields(),
-                title: '{% trans "Edit Line" %}',
-                onSuccess: reloadTable,
-            });
-        });
-
-        // Callback for deleting lines
-        $(table).find('.button-delete').click(function() {
-            var pk = $(this).attr('pk');
-
-            constructForm(`/api/order/so-extra-line/${pk}/`, {
-                method: 'DELETE',
-                title: '{% trans "Delete Line" %}',
-                onSuccess: reloadTable,
-            });
-        });
-    }
-
-    $(table).inventreeTable({
-        onPostBody: setupCallbacks,
-        name: 'salesorderextraline',
-        sidePagination: 'client',
-        formatNoMatches: function() {
-            return '{% trans "No matching lines" %}';
-        },
-        queryParams: filters,
-        original: options.params,
-        url: options.url,
-        showFooter: true,
-        uniqueId: 'pk',
-        detailViewByClick: false,
-        columns: columns,
-    });
-}