How to Create a Basic Form Using Formily's Core Package

To create a basic form using Formily's core package, instantiate a Form object by calling createForm() from @formily/core, then connect it to your UI layer using FormProvider and declare fields with SchemaField from your framework-specific binding.

The @formily/core package in the alibaba/formily repository provides the foundational state management, validation engine, and lifecycle hooks required to build robust forms programmatically. While the core package operates independently of any rendering layer, it is designed to pair with UI bindings like @formily/react or @formily/vue to handle the actual DOM representation. This guide walks through the essential steps to create a basic form using Formily's core package, referencing specific implementation details from the source code.

Instantiating the Form with createForm

The entry point for creating any Formily form is the createForm factory function exported from the core package. This function initializes a Form instance that manages field values, validation state, and submission lifecycle.

According to the source code in [packages/core/src/shared/externals.ts](https://github.com/alibaba/formily/blob/formily_next/packages/core/src/shared/externals.ts#L36) (line 36), createForm returns a Form object configured with your specified options. The resulting instance implements methods such as setValues, validate, and submit, which are defined in the core Form class at [packages/core/src/models/Form.ts](https://github.com/alibaba/formily/blob/formily_next/packages/core/src/models/Form.ts).

import { createForm } from '@formily/core';

const form = createForm({
  validateFirst: true,
  initialValues: {
    username: '',
    password: ''
  }
});

The Form instance created here acts as the single source of truth for all field states, errors, and effects, but it does not render any UI elements on its own.

Defining Fields with SchemaField

To declare form fields declaratively, Formily uses a schema-based approach via the SchemaField component provided by UI bindings (e.g., @formily/react). The schema itself is a plain JSON object that describes field types, validation rules, and component mappings using x-component and x-decorator properties.

While the core package in packages/core/src/models/Field.ts defines the underlying Field class that tracks individual field state, the SchemaField component translates your declarative schema into these core field instances.

import { SchemaField } from '@formily/react';

<SchemaField>
  <SchemaField.String
    name="username"
    title="Username"
    required
    x-decorator="FormItem"
    x-component="Input"
  />
  <SchemaField.String
    name="password"
    title="Password"
    required
    x-decorator="FormItem"
    x-component="Password"
  />
</SchemaField>

Each SchemaField declaration automatically registers a corresponding field instance with the core Form object, linking form.values.username and form.values.password to the respective components.

Connecting to the UI Layer with FormProvider

To make the core Form instance accessible to all descendant components, you must wrap your form layout with FormProvider (or the equivalent for your framework). This component creates a context that supplies the Form object to SchemaField components and action triggers like Submit.

As implemented in the React binding documentation, FormProvider accepts the form instance as a required prop:

import { FormProvider, Submit } from '@formily/react';

export default function BasicForm() {
  return (
    <FormProvider form={form}>
      <SchemaField>
        {/* field definitions */}
      </SchemaField>
      
      <Submit onSubmit={values => console.log(values)}>
        Submit
      </Submit>
    </FormProvider>
  );
}

The Submit component triggers form.submit() internally, which validates all fields according to the rules defined in the schema and executes the onSubmit callback with the validated values upon success.

Complete Working Example

Below is a complete, runnable example demonstrating how to create a basic form using Formily's core package with the React UI binding. This setup registers Ant Design components and creates a functional login form.

// FormComponent.tsx
import React from 'react';
import { createForm } from '@formily/core';
import { FormProvider, SchemaField, Submit } from '@formily/react';
import { Input, Password, FormItem } from '@formily/antd';

// Register UI components to SchemaField types
SchemaField.define('String', {
  component: Input,
  decorator: FormItem,
});
SchemaField.define('Password', {
  component: Password,
  decorator: FormItem,
});

const form = createForm({
  validateFirst: true,
});

export default function BasicForm() {
  return (
    <FormProvider form={form}>
      <SchemaField>
        <SchemaField.String
          name="username"
          title="Username"
          required
        />
        <SchemaField.Password
          name="password"
          title="Password"
          required
        />
      </SchemaField>

      <Submit onSubmit={values => alert(JSON.stringify(values, null, 2))}>
        Submit
      </Submit>
    </FormProvider>
  );
}

This implementation follows the three-step workflow: (1) createForm instantiates the core state manager, (2) FormProvider injects it into the component tree, and (3) SchemaField declarations map UI components to core field instances.

Core Architecture Concepts

Understanding the underlying classes helps when customizing behavior beyond basic schema definitions.

Summary

  • Initialize your form by calling createForm() from @formily/core, which returns a Form instance defined in packages/core/src/models/Form.ts.
  • Connect the form to your components using FormProvider from your UI binding (e.g., @formily/react).
  • Declare fields using SchemaField components, which map to the core Field class in packages/core/src/models/Field.ts.
  • Handle submission and validation through the Form instance methods or the Submit component, which orchestrates form.submit() and form.validate().

Frequently Asked Questions

What is the difference between @formily/core and @formily/react?

@formily/core is a framework-agnostic library that manages form state, validation logic, and field relationships through classes like Form and Field. @formily/react is a UI binding that provides React components (FormProvider, SchemaField, Submit) to render the core state. You need both to create a rendered form in a React application, but the core package can operate independently for headless form logic.

How do I access form values programmatically without using SchemaField?

You can interact directly with the Form instance created by createForm(). Access form.values to read the current state, or call form.setValuesIn('fieldName', value) to update specific fields. These methods are implemented in packages/core/src/models/Form.ts and allow imperative control when declarative schema definitions are insufficient.

Can I use @formily/core without a UI framework binding?

Yes, @formily/core is designed to be headless and can manage form state in non-UI environments such as Node.js servers or testing frameworks. You would manually instantiate fields using the Field class constructors and manage their lifecycle through the Form instance methods without ever mounting a FormProvider or SchemaField.

Where are validation rules processed in the Formily core?

Validation logic is orchestrated by the Form class methods and executed at the field level through the Field class in packages/core/src/models/Field.ts. When form.validate() is called, it triggers validation across all registered field instances, aggregating results into the form's error state before completing the submission lifecycle.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →