SharePoint  

Create SharePoint Lists and Columns in SPFx Using PnPjs

Overview

In this article, you'll learn how to.

  1. Set up the latest PnPjs (spfi) for SharePoint Framework (SPFx).
  2. Programmatically create a new SharePoint list.
  3. 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