Format every table in the active Google Doc A developer created a Google Apps Script that formats every table in a Google Doc with Arial 8pt font, a blue header row, alternating gray body rows, and zero cell padding. The script also uses the advanced Docs API to set white borders, disable pagination controls, and adjust paragraph spacing outside tables. | / | | | Format every table in the active Google Doc. | | | | | | - Body text: Arial 8 | | | - Header row: 0b5394 fill, white bold Arial 8 | | | - Cell padding: 0 on all sides | | | - Text edges: 0.02" inset on left & right via paragraph indents | | | the blue ruler controls , not via cell padding. | | | | | | Run formatAllTables . First run will prompt for authorization. | | | / | | | // 0.02 inch, expressed in points 1 in = 72 pt . | | | var TEXT INSET PT = 0.02 72; // = 1.44 pt | | | var FONT FAMILY = 'Arial'; | | | var FONT SIZE = 8; | | | var HEADER FILL = ' 0b5394'; | | | var HEADER TEXT = ' ffffff'; | | | // Body rows alternate between these two fills first body row gets 0 . | | | var ROW FILLS = ' f9f9f9', ' efefef' ; | | | function formatAllTables { | | | var body = DocumentApp.getActiveDocument .getBody ; | | | var tables = body.getTables ; | | | for var t = 0; t < tables.length; t++ { | | | formatTable tables t , t + 1 ; | | | } | | | // Borders are set via the advanced Docs API DocumentApp can't override | | | // per-cell borders . Requires the "Docs API" advanced service enabled. | | | setWhiteBorders ; | | | // Turn off space-hungry pagination controls across the whole doc. | | | setCompactPagination ; | | | // Body paragraphs outside tables : 0 before / 4 after. | | | setBodyParagraphSpacing ; | | | } | | | / | | | Set spacing to 0pt before / 4pt after on every paragraph OUTSIDE tables. | | | Top-level paragraph elements are, by definition, not inside table cells, | | | so iterating them leaves the table spacing rules untouched. | | | | | | REQUIRES the "Google Docs API" advanced service. | | | / | | | function setBodyParagraphSpacing { | | | var docId = DocumentApp.getActiveDocument .getId ; | | | var doc = Docs.Documents.get docId ; | | | var content = doc.body.content || ; | | | var requests = ; | | | for var i = 0; i < content.length; i++ { | | | var el = content i ; | | | if el.paragraph continue; // skip tables, section breaks, etc. | | | requests.push { | | | updateParagraphStyle: { | | | paragraphStyle: { | | | spaceAbove: { magnitude: 0, unit: 'PT' }, | | | spaceBelow: { magnitude: 4, unit: 'PT' } | | | }, | | | fields: 'spaceAbove,spaceBelow', | | | range: { startIndex: el.startIndex, endIndex: el.endIndex } | | | } | | | } ; | | | } | | | if requests.length 0 { | | | Docs.Documents.batchUpdate { requests: requests }, docId ; | | | Logger.log 'Set 0/4 spacing on %s body paragraph s .', requests.length ; | | | } | | | } | | | / | | | Disable the four "space-hungry" pagination settings on EVERY paragraph | | | in the document body + tables : Keep with next, Keep lines together, | | | Prevent single lines widow/orphan , and Page break before. | | | | | | REQUIRES the "Google Docs API" advanced service. | | | / | | | function setCompactPagination { | | | var docId = DocumentApp.getActiveDocument .getId ; | | | var doc = Docs.Documents.get docId ; | | | var content = doc.body.content; | | | if content || content.length === 0 return; | | | var endIndex = content content.length - 1 .endIndex; | | | // These three are allowed on paragraphs inside tables, so apply doc-wide. | | | var requests = { | | | updateParagraphStyle: { | | | paragraphStyle: { | | | keepWithNext: false, | | | keepLinesTogether: false, | | | avoidWidowAndOrphan: false | | | }, | | | fields: 'keepWithNext,keepLinesTogether,avoidWidowAndOrphan', | | | range: { startIndex: 1, endIndex: endIndex - 1 } | | | } | | | } ; | | | // pageBreakBefore is rejected on in-table paragraphs, so only target | | | // top-level paragraphs which are never inside a table . | | | for var i = 0; i < content.length; i++ { | | | var el = content i ; | | | if el.paragraph continue; | | | requests.push { | | | updateParagraphStyle: { | | | paragraphStyle: { pageBreakBefore: false }, | | | fields: 'pageBreakBefore', | | | range: { startIndex: el.startIndex, endIndex: el.endIndex } | | | } | | | } ; | | | } | | | Docs.Documents.batchUpdate { requests: requests }, docId ; | | | Logger.log 'Disabled keep-with-next / keep-together / widow-orphan doc-wide; page-break-before on body paragraphs.' ; | | | } | | | function formatTable table, tableNum { | | | var numRows = table.getNumRows ; | | | // Top-left cell text row 1, cell 1 , for identifying tables in the log. | | | var firstCellText = ' empty '; | | | if numRows 0 && table.getRow 0 .getNumCells 0 { | | | firstCellText = table.getRow 0 .getCell 0 .getText ; | | | } | | | // Figure tables hold images in 1 or 2 cells — leave them untouched. | | | var totalCells = 0; | | | for var i = 0; i < numRows; i++ { | | | totalCells += table.getRow i .getNumCells ; | | | } | | | if totalCells <= 2 { | | | Logger.log 'Table %s: SKIPPED %s cells — cell 1,1 : "%s"', tableNum, totalCells, firstCellText ; | | | return; | | | } | | | Logger.log 'Table %s: formatting %s cells — cell 1,1 : "%s"', tableNum, totalCells, firstCellText ; | | | for var r = 0; r < numRows; r++ { | | | var row = table.getRow r ; | | | var numCells = row.getNumCells ; | | | var isHeader = r === 0 ; | | | // Body row index is r-1 so the first body row gets ROW FILLS 0 . | | | var bodyFill = isHeader ? null : ROW FILLS r - 1 % ROW FILLS.length ; | | | for var c = 0; c < numCells; c++ { | | | var cell = row.getCell c ; | | | formatCell cell, isHeader, bodyFill ; | | | } | | | } | | | } | | | function formatCell cell, isHeader, bodyFill { | | | // Zero cell padding on all sides. | | | cell.setPaddingTop 0 ; | | | cell.setPaddingBottom 0 ; | | | cell.setPaddingLeft 0 ; | | | cell.setPaddingRight 0 ; | | | // Vertical centering is done via the Docs API in setWhiteBorders — | | | // DocumentApp's setVerticalAlignment silently no-ops here. | | | // Fill: header color, otherwise the alternating body color. | | | if isHeader { | | | cell.setBackgroundColor HEADER FILL ; | | | } else { | | | cell.setBackgroundColor bodyFill ; | | | } | | | // Text attributes for everything inside the cell. | | | var textAttrs = {}; | | | textAttrs DocumentApp.Attribute.FONT FAMILY = FONT FAMILY; | | | textAttrs DocumentApp.Attribute.FONT SIZE = FONT SIZE; | | | textAttrs DocumentApp.Attribute.BOLD = isHeader; | | | if isHeader { | | | textAttrs DocumentApp.Attribute.FOREGROUND COLOR = HEADER TEXT; | | | } else { | | | textAttrs DocumentApp.Attribute.FOREGROUND COLOR = null; // leave default | | | } | | | // Paragraph attributes: inset the text edges 0.02" on both sides | | | // using indents the blue ruler markers , since padding is 0. | | | var paraAttrs = {}; | | | paraAttrs DocumentApp.Attribute.INDENT START = TEXT INSET PT; | | | paraAttrs DocumentApp.Attribute.INDENT END = TEXT INSET PT; | | | paraAttrs DocumentApp.Attribute.INDENT FIRST LINE = TEXT INSET PT; | | | // Headers centered, body left-justified. | | | paraAttrs DocumentApp.Attribute.HORIZONTAL ALIGNMENT = isHeader | | | ? DocumentApp.HorizontalAlignment.CENTER | | | : DocumentApp.HorizontalAlignment.LEFT; | | | // Single line spacing inside tables. | | | paraAttrs DocumentApp.Attribute.LINE SPACING = 1; | | | var numChildren = cell.getNumChildren ; | | | for var i = 0; i < numChildren; i++ { | | | var child = cell.getChild i ; | | | var type = child.getType ; | | | if type === DocumentApp.ElementType.PARAGRAPH { | | | var para = child.asParagraph ; | | | para.setAttributes paraAttrs ; | | | para.setSpacingAfter 0 ; // no space after plain paragraphs/line breaks | | | para.editAsText .setAttributes textAttrs ; | | | } else if type === DocumentApp.ElementType.LIST ITEM { | | | var li = child.asListItem ; | | | li.setAttributes paraAttrs ; | | | li.setSpacingAfter 2 ; // 2pt after bullets | | | li.editAsText .setAttributes textAttrs ; | | | } else if type === DocumentApp.ElementType.TABLE { | | | // Nested table — getTables doesn't return these, so recurse. | | | formatTable child.asTable , 'nested' ; | | | } | | | } | | | } | | | / | | | Set every cell border to white 0.5pt via the advanced Docs API. | | | DocumentApp can't reliably override per-cell borders, so we use | | | updateTableCellStyle, which does. | | | | | | REQUIRES: Apps Script editor → Services + → add "Google Docs API". | | | / | | | function setWhiteBorders { | | | var docId = DocumentApp.getActiveDocument .getId ; | | | var doc = Docs.Documents.get docId ; | | | var requests = ; | | | collectBorderRequests doc.body.content, requests ; | | | if requests.length 0 { | | | Docs.Documents.batchUpdate { requests: requests }, docId ; | | | Logger.log 'Applied white borders to %s table s .', requests.length ; | | | } else { | | | Logger.log 'No eligible tables found for borders.' ; | | | } | | | } | | | // Walk structural elements, emit one border request per eligible table | | | // and recurse into nested tables . Mirrors the <=2-cell skip rule. | | | function collectBorderRequests content, requests { | | | if content return; | | | for var i = 0; i < content.length; i++ { | | | var el = content i ; | | | if el.table continue; | | | var table = el.table; | | | var tableRows = table.tableRows || ; | | | // Count cells; skip figure tables 1 or 2 cells total . | | | var totalCells = 0; | | | for var r = 0; r < tableRows.length; r++ { | | | totalCells += tableRows r .tableCells || .length; | | | } | | | if totalCells <= 2 continue; | | | var whiteBorder = { | | | color: { color: { rgbColor: { red: 1, green: 1, blue: 1 } } }, | | | width: { magnitude: 0.5, unit: 'PT' }, | | | dashStyle: 'SOLID' | | | }; | | | requests.push { | | | updateTableCellStyle: { | | | tableCellStyle: { | | | borderTop: whiteBorder, | | | borderBottom: whiteBorder, | | | borderLeft: whiteBorder, | | | borderRight: whiteBorder, | | | contentAlignment: 'MIDDLE' | | | }, | | | fields: 'borderTop,borderBottom,borderLeft,borderRight,contentAlignment', | | | tableStartLocation: { index: el.startIndex } | | | } | | | } ; | | | // Recurse into nested tables inside each cell. | | | for var rr = 0; rr < tableRows.length; rr++ { | | | var cells = tableRows rr .tableCells || ; | | | for var cc = 0; cc < cells.length; cc++ { | | | collectBorderRequests cells cc .content, requests ; | | | } | | | } | | | } | | | } |