Overview
In this article, you'll learn how to.
- Set up the latest PnPjs (spfi) for SharePoint Framework (SPFx).
- Programmatically create a new SharePoint list.
- Add custom columns of various types.
- Text
- Number
- Choice & MultiChoice
- DateTime
- Boolean (Yes/No)
- Lookup
- Person or Group
- Hyperlink or Picture
This is perfect for provisioning SharePoint assets as part of your SPFx solution, such as app installation scripts, admin dashboards, or deployment tools.
Prerequisites
Ensure you've already configured SPFI in your SPFx solution.
import { spfi, SPFI } from "@pnp/sp";
import { SPFx } from "@pnp/sp/presets/all";
let sp: SPFI;
protected async onInit(): Promise<void> {
await super.onInit();
sp = spfi().using(SPFx(this.context));
}
Step 1. Create a SharePoint List
Basic Custom List
await sp.web.lists.add(
"EmployeeList",
"Stores employee records",
100 // 100 = GenericList
);
Hidden / Read-Only / Template Types
You can pass more options, like.
await sp.web.lists.add(
"ProjectDocs",
"Stores project documents",
101,
false,
{
EnableVersioning: true,
Hidden: false,
}
);
- 100: Generic list
- 101: Document Library
- 102: Discussion Board
More template types you can find here.
Step 2. Add Columns to the List
Let’s say we created EmployeeList. Now, let’s add different types of columns.
- Single Line of Text
await sp.web.lists
.getByTitle("EmployeeList")
.fields
.addText("Title", 255);
- Number
await sp.web.lists
.getByTitle("EmployeeList")
.fields
.addNumber("EmployeeID");
- Choice Field
await sp.web.lists
.getByTitle("EmployeeList")
.fields.addChoice("EmpType", {
Choices: ["Full-Time", "Part-Time", "Contractor"],
DefaultFormula: `"Full-Time"`
});
- MultiChoice Field
await sp.web.lists
.getByTitle("EmployeeList")
.fields
.addMultiChoice("Skills", {
Choices: ["React", "PnPjs", "SPFx", "Power Automate"]
});
- Yes/No (Boolean)
await sp.web.lists.getByTitle("EmployeeList").fields.addBoolean("IsActive");
- DateTime
await sp.web.lists
.getByTitle("EmployeeList")
.fields
.addDateTime("JoiningDate", { DisplayFormat: DateOnly });
- Person or Group
await sp.web.lists
.getByTitle("EmployeeList")
.fields
.addUser("Manager");
- Multi-Person
await sp.web.lists
.getByTitle("EmployeeList")
.fields
.addUser("Team", {
AllowMultipleValues: true,
SelectionMode: FieldUserSelectionMode.PeopleOnly
});
- Lookup: First, create a source list like Departments with an ID + Title.
await sp.web.lists
.getByTitle("EmployeeList")
.fields.addLookup("Department", {
LookupListId: "<GUID-of-Departments-List>",
LookupFieldName: "Title"
});
- You can get the LookupListId like.
const list = await sp.web.lists.getByTitle("Departments")();
console.log(list.Id);
- Hyperlink or Picture
await sp.web.lists
.getByTitle("EmployeeList")
.fields.addUrl("ProfileLink", {
DisplayFormat: UrlFieldFormatType.Hyperlink,
});
Bonus: Add Site Column and Then Attach
Create reusable site columns.
await sp.web.fields.addText("GlobalTextField");
Then attach it to a list.
await sp.web.lists
.getByTitle("EmployeeList")
.fields
.addFieldAsXml(`
<Field
Type="Text"
Name="GlobalTextField"
DisplayName="Global Text"
/>
`);
Conclusion
With the modern PnPjs (spfi) API, provisioning SharePoint lists and fields directly from your SPFx solution becomes simple, repeatable, and code-driven. Whether you're building tools for admins or initializing your app on first launch, this approach ensures your list schema is always consistent.
In the next post, we’ll explore how to.
- Check if a list or field already exists before creating it.
- Conditionally update schema.
- Automate list + content type creation from a config object or JSON model.
Resources