Why did Salesforce choose this approach with the column definition of the lightning-datatable component?

const columns = [
    { label: 'Label', fieldName: 'name' },
    { label: 'Website', fieldName: 'website', type: 'url' },
    { label: 'Phone', fieldName: 'phone', type: 'phone' },
    { label: 'Balance', fieldName: 'amount', type: 'currency' },
    { label: 'CloseAt', fieldName: 'closeAt', type: 'date' },

I was lately thinking about streamlining the definition using a Builder pattern, so we could build the columns like this for example:

return buildColumns([
    new TextColumnBuilder('ApexClass.Name', 'Class Name').setInitialWidth(300),
    new TextColumnBuilder('Status', 'Status').setInitialWidth(110),
    new DateColumnBuilder('CreatedDate', 'Created Date').setInitialWidth(160).setYear('numeric').setMonth('2-digit').setDay('2-digit').setHour('2-digit').setMinute('2-digit'),
    new DateColumnBuilder('CompletedDate', 'Completed Date').setInitialWidth(160).setYear('numeric').setMonth('2-digit').setDay('2-digit').setHour('2-digit').setMinute('2-digit'),
    new NumberColumnBuilder('TotalJobItems', 'Total').setCellAttributes({ alignment: 'left' }),
    new NumberColumnBuilder('JobItemsProcessed', 'Processed').setCellAttributes({ alignment: 'left' }),
    new NumberColumnBuilder('NumberOfErrors', 'Errors').setCellAttributes({ alignment: 'left' }),
    new TextColumnBuilder('CreatedBy.Name', 'Submitted By').setInitialWidth(150),
    new ButtonIconColumnBuilder('abort').setInitialWidth(50).setIconName('utility:close').setDisabled({ fieldName: 'isDisabled' }).setVariant('destructive-text'),

What would be the benefits of such approach? Does anyone have experience with something similar?

1 Answer 1


In JavaScript, we typically define functions with zero or one main parameters, and any number of optional parameters defined as an Object. This allows us to have great flexibility in writing methods that can accept a large number of optional parameters. For example, looking at fetch, we see that the one required parameter is the endpoint, and all of the options cover things like the method to use, the headers to present, and so on.

Builder patterns are useful for strongly typed languages like Apex or Java, but are typically not as useful for loosely typed languages like JavaScript or Ruby. That's because the interpreters/compilers for these languages don't necessarily always know what the data type of a variable will be ahead of time. Accordingly, that's one reason why TypeScript was invented, to present additional data characteristics to the programmer, and introduce strong typing in a loosely typed language.

You can see how LWC uses TypeScript to provide bindings that will appear as you write LWC code. The .sfdx/typings/lwc/ folder is autogenerated and includes bindings for things like @salesforce/apex, lwc, and so on. These bindings allow eslint to throw errors ahead of time if you try to use a method or object that doesn't exist. This mechanism is also partially how your IDE can show you the expected parameters of a function and provide auto-complete suggestions.

Builder patterns do provide some nice compile-time benefits, but they rarely perform better than native object notation for languages that support them, often do not provide any compile-time or run-time safety in loosely typed languages, and generally are far more verbose--longer and more difficult to read--than the object notation. Of course, for something as trivial as a column descriptor, I'd say it's largely irrelevant, but your code isn't anything that mainstream JavaScript developers would do on a daily basis. One of JavaScript's advantages is being able to produce compact code that executes well, and the builder pattern explicitly breaks this design goal.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .