← Back to Blog

SharePoint Column Formatting: A Practical JSON Guide (2026)

SharePoint column formatting JSON explained: @currentField, [$Field] references, eight copy-paste presets by column type and a PnP PowerShell deploy (2026).

SharePoint Column Formatting: A Practical JSON Guide (2026)


SharePoint column formatting is a JSON description of how one column should render in a list view. You paste it into the column's Format this column panel and SharePoint validates it against the column-formatting schema. Everyone who views the list sees the new rendering immediately (Microsoft Learn: Use column formatting to customize SharePoint). No SPFx package, no deployment, no tenant app catalog.

This guide is the fast path. It covers the schema's six properties, the three token forms you need (@currentField, [$InternalName], @now), and eight presets for Choice, Number (two of them), Date, Person, Yes/No, Hyperlink and cross-column styling. It ends with the PnP PowerShell command that applies the same JSON across sites. Want to test a JSON column formatting snippet before pasting it? The SharePoint JSON Column Formatter renders your JSON live against sample values, entirely in the browser.

What is SharePoint Column Formatting?

Column formatting is declarative JSON that controls the HTML elements, inline styles and conditional logic SharePoint uses to render a single column. It does not change the stored value. A number column still sorts, filters and totals as a number even when it renders as a progress bar.

Use column formatting when you want badges, bars, icons or conditional colour on one field and you need it in minutes.

Use view formatting when the whole row should change (background by status, card layouts). View formatting uses the same expression language, so everything below transfers.

Use SPFx field customizers when you need code: calling an API, React state, or anything the schema cannot express. Microsoft spent 2025 evaluating whether to retire field customizers and currently says it has no plans to, but column formatting is the safer default for anything the schema can express. The decision table in Microsoft Lists JSON Formatting: Complete Guide (2026) covers the three options side by side.

How to Apply Column Formatting

Prerequisite: anyone who can create and manage views in the list can apply column formatting. On a default team site that is the Edit permission level or higher; no admin role is needed.

  • Open the list and click the column header's dropdown arrow.

  • Choose Column settings → Format this column.

  • The panel opens with type-specific quick options (Fill color for Choice, Date and Yes/No columns, Data bars for Number columns). Click Advanced mode to get the JSON editor.

  • Paste the JSON, click Preview to check the live result in the list behind the panel, then click Save.

The formatting is stored on the field itself (its CustomFormatter property), which is why it follows the column into every view, including views you create later.

The JSON Schema in Six Properties

Every column-formatting file is a tree of elements. Each element supports the same small set of properties, all documented in the formatting syntax reference:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "txtContent": "@currentField",
  "style": {
    "background-color": "#f3f2f1",
    "padding": "4px 8px",
    "border-radius": "4px"
  },
  "attributes": {
    "class": "ms-fontWeight-semibold"
  },
  "children": []
}
  • $schema points at the v2 schema. VS Code and the Advanced mode editor use it for IntelliSense and validation; keep it as the first line.

  • elmType is the HTML element to render. The reference allows exactly nine: div, span, a, img, svg, path, button, p and filepreview. Any other value is an error.

  • txtContent is the text inside the element. A plain string renders literally; a string that starts with = is evaluated as an expression. If an element has txtContent, its children are ignored.

  • style holds inline CSS from an allow-list of properties. Names are the standard hyphenated CSS names, so it is "background-color", not backgroundColor. Values can be expressions.

  • attributes sets HTML attributes from a fixed list of fourteen: href, src, class, target, title, role, iconName, d, aria, rel, viewBox, data-interception, preserveAspectRatio and draggable. Any other attribute name is an error.

  • children nests further elements. This is how a progress bar is built: an outer track div with an inner fill div.

Referencing values: the three tokens that matter

  • @currentField is the value of the column being formatted. For person and lookup columns, read a sub-property: @currentField.title, @currentField.email, @currentField.lookupValue.

  • [$FieldInternalName] reads another column in the same row. It is the internal name, not the display name: a column shown as "Due Date" that was created with a space is [$Due_x0020_Date]. Check the internal name on the column's settings page before you write the expression.

  • @now is the current date and time when the view loads, and @me is the current user's email. Both are used bare inside expressions, for example =if([$DueDate] <= @now, 'overdue', '').

Expressions use an Excel-style syntax: =if(@currentField == 'Done', '#107c10', '#a19f9d'). The reference lists the comparison operators ==, !=, <, <=, >, >=, logic with && and ||, single-quoted strings, and nested if() calls in place of switch.

Preset 1: Status Badge with Fluent Icons (Choice column)

The cleanest status badge uses SharePoint's built-in severity classes instead of hand-picked colours, and iconName pulls a Fluent UI icon without any image hosting. This follows the pattern in Microsoft's own sample: the class goes on the outer div, the icon on one child span, the text on another.

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "attributes": {
    "class": "=if(@currentField == 'Done', 'sp-field-severity--good', if(@currentField == 'In progress', 'sp-field-severity--low', if(@currentField == 'In review', 'sp-field-severity--warning', if(@currentField == 'Blocked', 'sp-field-severity--blocked', 'sp-field-severity--severeWarning')))) + ' ms-fontColor-neutralSecondary'"
  },
  "children": [
    {
      "elmType": "span",
      "style": {
        "display": "inline-block",
        "padding": "0 4px"
      },
      "attributes": {
        "iconName": "=if(@currentField == 'Done', 'CheckMark', if(@currentField == 'In progress', 'Forward', if(@currentField == 'In review', 'Warning', if(@currentField == 'Blocked', 'ErrorBadge', 'Error'))))"
      }
    },
    {
      "elmType": "span",
      "txtContent": "@currentField"
    }
  ]
}

Apply it to a Choice column whose options match the strings in the expression exactly. The five severity classes are sp-field-severity--good, --low, --warning, --severeWarning and --blocked. Change the option text in both the class and iconName expressions when you rename a choice.

Preset 2: Progress Bar from a Number Column

A percentage column (0 to 100) becomes a bar with the value printed over it. The outer div is the track, the first child is the fill, the second child is the label.

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "style": {
    "position": "relative",
    "width": "100%",
    "height": "20px",
    "background-color": "#e1dfdd",
    "border-radius": "10px",
    "overflow": "hidden"
  },
  "children": [
    {
      "elmType": "div",
      "style": {
        "width": "=if(@currentField > 100, '100%', if(@currentField < 0, '0%', @currentField + '%'))",
        "height": "100%",
        "background-color": "=if(@currentField >= 80, '#107c10', if(@currentField >= 50, '#0078d4', if(@currentField >= 25, '#ffb900', '#d13438')))"
      }
    },
    {
      "elmType": "span",
      "txtContent": "=@currentField + '%'",
      "style": {
        "position": "absolute",
        "top": "0",
        "left": "0",
        "right": "0",
        "text-align": "center",
        "font-size": "11px",
        "line-height": "20px",
        "font-weight": "600"
      }
    }
  ]
}

The clamp in the fill's width expression stops a typo like 250 from blowing the bar past its track. If the column stores a fraction (0.75) rather than a percentage, multiply first: =(@currentField * 100) + '%'.

Preset 3: Data Bar with the Built-in Class (Number column)

For "bigger number, longer bar" without a label overlay, SharePoint ships a class for exactly that. sp-field-dataBars draws a themed bar behind the text, and you only control the width.

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "txtContent": "@currentField",
  "attributes": {
    "class": "sp-field-dataBars"
  },
  "style": {
    "padding": "0 4px",
    "width": "=if(@currentField >= 20, '100%', (@currentField * 5) + '%')"
  }
}

This example treats 20 as the full-width value (20 × 5 = 100%). For a column where the realistic maximum is 500, use (@currentField / 5) + '%' and clamp at 500 the same way. Data bars work well on effort estimates, counts and currency columns where the relative size is the point.

Preset 4: Due Date Highlighting with @now (Date column)

Highlight overdue items in red and anything due inside a week in amber. The comparison is between the date column and @now; addDays() shifts the threshold.

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "txtContent": "=toLocaleDateString(@currentField)",
  "attributes": {
    "class": "=if(Number(@currentField) == 0, '', if(@currentField <= @now, 'sp-field-severity--severeWarning', if(@currentField <= addDays(@now, 7), 'sp-field-severity--warning', '')))"
  },
  "style": {
    "font-weight": "=if(Number(@currentField) != 0 && @currentField <= @now, '600', '400')"
  }
}

toLocaleDateString() formats the date for the viewer's locale instead of the raw ISO string. The outer if(Number(@currentField) == 0, '', ...) guard keeps blank date cells from picking up the overdue class: an empty date converts to 0, which is the test the PnP date-check-blank-format sample uses.

Preset 5: Person Column with Photo

A Person column exposes @currentField.title (display name) and @currentField.email. SharePoint's /_layouts/15/userphoto.aspx endpoint (the URL the PnP person-roundimage-format sample uses) turns the email into a thumbnail, so the cell shows a round photo next to the name.

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "style": {
    "display": "flex",
    "align-items": "center"
  },
  "children": [
    {
      "elmType": "img",
      "style": {
        "width": "28px",
        "height": "28px",
        "border-radius": "50%",
        "margin-right": "8px"
      },
      "attributes": {
        "src": "='/_layouts/15/userphoto.aspx?size=S&accountname=' + @currentField.email",
        "title": "@currentField.title"
      }
    },
    {
      "elmType": "span",
      "txtContent": "@currentField.title"
    }
  ]
}

For a multi-value Person column, wrap the image and name in an element that carries "forEach": "person in @currentField" and read [$person.email] and [$person.title] inside it. Without forEach the element is not repeated per person (see "Formatting multi-value fields" on the Learn page). Other sub-properties on the token are id, sip, picture, department and jobTitle.

Preset 6: Yes/No Column as an Icon

A Yes/No column evaluates to true or false, so the expression needs no comparison. This renders a green check for Yes and a grey cancel icon for No.

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "attributes": {
    "class": "=if(@currentField, 'sp-field-severity--good', '')"
  },
  "children": [
    {
      "elmType": "span",
      "style": {
        "display": "inline-block",
        "padding": "0 4px"
      },
      "attributes": {
        "iconName": "=if(@currentField, 'CheckMark', 'Cancel')"
      }
    },
    {
      "elmType": "span",
      "txtContent": "=if(@currentField, 'Yes', 'No')"
    }
  ]
}

Swap the two strings for 'Approved' / 'Pending' or any pair that reads better than Yes/No in your list.

Preset 7: Hyperlink Column as a Button

On a Hyperlink column, @currentField is the URL and @currentField.desc is the description text. This renders the description as a link that opens in a new tab, which is cleaner than the default full-URL display.

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "a",
  "txtContent": "=if(@currentField.desc == '', 'Open link', @currentField.desc)",
  "attributes": {
    "href": "@currentField",
    "target": "_blank",
    "class": "ms-fontColor-themePrimary"
  }
}

Set txtContent to a fixed string such as 'Open ticket' when every row points at the same system and the stored descriptions are inconsistent.

Preset 8: Reading Other Columns in the Row

Column formatting can read any column in the same row with [$InternalName], which is how you style one field by another's value. This renders the Title column bold and red when the row's Priority is High:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "txtContent": "=if([$Priority] == 'High', '⚠ ' + @currentField, @currentField)",
  "style": {
    "font-weight": "=if([$Priority] == 'High', '700', '400')",
    "color": "=if([$Priority] == 'High', '#d13438', 'inherit')"
  }
}

Two rules: the referenced column has to be in the view for the value to be available, and the internal name is what counts. If [$Priority] renders as empty text, check the column's internal name on its settings page.

Deploy the Same JSON with PnP PowerShell

Pasting JSON into a panel is fine for one list. For a template you roll out to many sites, set the field's CustomFormatter property with PnP PowerShell's Set-PnPField instead. Since 9 September 2024, Connect-PnPOnline -Interactive requires your own Entra app registration passed as -ClientId (the PnP PowerShell cmdlet docs spell out the change). Create that registration once with Register-PnPEntraIDApp:

$json = Get-Content .\status-badge.json -Raw

$siteUrls = @(
  "https://contoso.sharepoint.com/sites/projects",
  "https://contoso.sharepoint.com/sites/marketing"
)

foreach ($url in $siteUrls) {
  Connect-PnPOnline -Url $url -Interactive -ClientId <your-app-id>
  Set-PnPField -List "Tasks" -Identity "Status" -Values @{ CustomFormatter = $json }
}

Every Tasks list in the array gets the badge in one run; for a single site, drop the loop and keep the two cmdlets. The PnP Script Generator produces the connection and loop scaffolding; drop the Set-PnPField line inside it. To remove formatting, open Format this column, clear the Advanced mode editor and save.

Tips and Gotchas

Keep $schema on the first line. It is what gives the Advanced mode editor validation and IntelliSense, which is where most typos get caught.

Internal names, not display names. [$Due Date] does nothing. [$Due_x0020_Date] works. The column settings page shows the internal name.

Strings are single-quoted. Every documented example uses 'Done', not "Done"; stick to single quotes inside expressions.

Styles are hyphenated CSS. "backgroundColor" fails schema validation; "background-color" works.

txtContent wins over children. An element with txtContent ignores its children, so a badge with an icon and a label needs two child spans, as in Preset 1.

Test on a copy of the list first. A wrong expression renders every row as blank text until you fix it. A column on a test list, or the live preview in the JSON Column Formatter, shows the result before production users do.

Frequently Asked Questions

Can one JSON file format several columns?
No. Column formatting is attached to a single field. Apply the JSON to each column separately, or switch to view formatting if the goal is row-level styling.

Does formatting change the stored data?
No. The value, its type, sorting and filtering are unchanged. Formatting only affects rendering in list views.

Does it apply to every view?
Yes. The JSON lives on the field, so every view that shows the column renders it. Forms are not affected; use the form's JSON configuration or an SPFx form customizer for that.

How do I remove formatting?
Open Format this column, switch to Advanced mode, delete the JSON, and save.

Where are more samples?
The community PnP List-Formatting sample repository holds roughly 190 column samples and 180 view samples as of October 2026, each with a screenshot and the JSON.

When Forms Need Full Custom UI

SharePoint column formatting handles the display side. When the new, edit and view forms themselves need a custom React UI, SPFx Form Customizer Extensions: Modern List Forms (2026) is the next step: full control over validation, layout and data flow.

Related Resources




Free Developer Tool

CAML Query Builder

Build complex CAML queries visually — no more hand-writing XML. Add nested AND/OR conditions, lookup filters, and get clean output instantly.

Try It Free →

We use cookies for analytics and personalised advertising. By accepting, you allow these uses. See our Privacy Policy and Cookie Policy.