Shesha Developer Guide
A comprehensive guide to developing configurable components in the Shesha low-code platform.
Prerequisites
- Node.js v14+
- npm or yarn
- Git
Getting Started
# Fork and clone the repository
git fork https://github.com/your-organization/shesha-framework.git
cd shesha-framework
# Install dependencies
cd shesha-reactjs
npm install
# Start development server
npm run dev
Architecture Overview
Shesha is a low-code platform built on:
- NextJS + TypeScript
- Ant Design components
Project Structure
shesha-framework/
├── shesha-reactjs/ # Frontend React project
│ ├── src/
│ │ ├── components/ # Reusable components
│ │ ├── designer-components/ # Form designer components
│ │ │ ├── _common/ # Shared utilities
│ │ │ ├── _settings/ # Settings components
│ │ │ └── textField/ # Example component
│ │ ├── hooks/ # Custom React hooks
│ │ ├── providers/ # Context providers
│ │ └── styles/ # Styling system
└── shesha-backend/ # Backend project
Component Development
Core Files Structure
Every Shesha component requires 3 essential files:
designer-components/myComponent/
├── index.tsx # Main component implementation
├── interfaces.ts # TypeScript interfaces
├── settingsForm.ts # Configuration settings
├── styles.ts # Component styling (optional)
└── utils.ts # Helper functions (optional)
1. Component Interface (interfaces.ts
)
import { IConfigurableFormComponent } from '@/providers/form/models';
export interface IMyComponentProps extends IConfigurableFormComponent {
placeholder?: string;
customValue?: string;
validate?: {
required?: boolean;
minLength?: number;
message?: string;
};
}
2. Settings Configuration (settingsForm.ts
)
import { DesignerToolbarSettings } from '@/interfaces/toolbarSettings';
import { nanoid } from '@/utils/uuid';
export const getSettings = (data: IMyComponentProps) => {
const commonTabId = nanoid();
return {
components: new DesignerToolbarSettings(data)
.addSearchableTabs({
id: nanoid(),
propertyName: 'settingsTabs',
parentId: 'root',
label: 'Settings',
hideLabel: true,
tabs: [{
key: '1',
title: 'Common',
id: commonTabId,
components: [
...new DesignerToolbarSettings()
.addSettingsInput({
id: nanoid(),
propertyName: 'placeholder',
label: 'Placeholder',
inputType: 'textField',
jsSetting: true,
})
.toJson()
]
}]
})
.toJson(),
formSettings: {
layout: 'vertical',
labelCol: { span: 24 },
wrapperCol: { span: 24 }
}
};
};
3. Component Implementation (index.tsx
)
import React from 'react';
import { IToolboxComponent } from '@/interfaces';
import { Input } from 'antd';
import { ComponentOutlined } from '@ant-design/icons';
import { IMyComponentProps } from './interfaces';
import { getSettings } from './settingsForm';
const MyComponent: IToolboxComponent<IMyComponentProps> = {
type: 'myComponent',
name: 'My Component',
icon: <ComponentOutlined />,
isInput: true,
Factory: ({ model }) => {
if (model.hidden) return null;
return (
<Input
placeholder={model.placeholder}
disabled={model.disabled}
/>
);
},
settingsFormMarkup: (data) => getSettings(data),
initModel: (model) => ({
...model,
label: 'My Component',
placeholder: 'Enter text...'
}),
migrator: (m) => m,
};
export default MyComponent;
Your component model contains properties primarily from the component's properties panel.
4. Component Registration
Add your component to
src/providers/form/defaults/toolboxComponents.ts
:
import MyComponent from '@/designer-components/myComponent';
export const getToolboxComponents = (): IToolboxComponentGroup[] => [
{
name: 'Data Entry',
visible: true,
components: [
TextField,
NumberField,
MyComponent, // Add your component here
],
},
// ... other groups
];
The
visible
property determines whether your component will appear in the widgets panel.
Settings System
Common Setting Types
Basic Input
.addSettingsInput({
id: nanoid(),
propertyName: 'label',
label: 'Label',
inputType: 'textField',
jsSetting: true,
})
Input Row (Multiple Fields)
.addSettingsInputRow({
id: nanoid(),
hidden: {
_code: 'return getSettingValue(data?.background?.type) !== "color";',
_mode: 'code',
_value: false
} as any,
parentId: appearanceTabId,
inputs: [{
type: 'colorPicker',
id: 'backgroundStyleRow-color',
label: "Color",
propertyName: "background.color",
hideLabel: true,
jsSetting: false,
}],
})
Collapsible Panel
.addCollapsiblePanel({
id: nanoid(),
propertyName: 'styling',
label: 'Styling Options',
content: {
id: nanoid(),
components: [
// Panel contents...
],
},
})
Conditional Visibility
Show/Hide Based on Boolean
hidden: {
_code: 'return getSettingValue(data?.background?.type) !== "color";',
_mode: 'code',
_value: false
} as any
Show Based on Selected Value
hidden: {
_code: 'return getSettingValue(data?.background?.type) === "color";',
_mode: 'code',
_value: false
} as any
Responsive Design with Property Router
.addPropertyRouter({
id: nanoid(),
propertyName: 'deviceSettings',
label: 'Device Settings',
propertyRouteName: {
_mode: 'code',
_code: "return contexts.canvasContext?.designerDevice || 'desktop';",
},
components: [
// Device-specific settings...
],
})
Styling System
Creating Styles (styles.ts
)
import { createStyles } from '@/styles';
export const useStyles = createStyles(({ css, cx }, props) => {
const myComponent = cx("sha-my-component", css`
.ant-input {
font-weight: ${props.fontWeight};
color: ${props.color};
border-radius: ${props.borderRadius}px;
}
`);
return { myComponent };
});
Using Styles in Components
import { useStyles } from './styles';
const MyComponent = (props) => {
const { classes } = useStyles({
fontWeight: props.font?.weight,
color: props.font?.color,
borderRadius: props.borderRadius,
});
return (
<div className={classes.myComponent}>
{/* Component content */}
</div>
);
};
Shesha Hooks
useForm
– Form Context
import { useForm } from '@/providers/form';
const { form, formMode, formData } = useForm();
// Read values
const value = form.getValues('fieldName');
// Set values
form.setFieldValue('fieldName', 'newValue');
// Submit form
form.submit();
useSheshaApplication
– App Services
import { useSheshaApplication } from '@/providers/sheshaApplication';
const { httpClient, backendUrl } = useSheshaApplication();
// Make API calls
const response = await httpClient.get(`${backendUrl}/api/data`);
useConfigurableAction
– Execute Actions
import { useConfigurableAction } from '@/providers/configurableActionsDispatcher';
const { execute, loading } = useConfigurableAction({
actionName: 'submit',
actionOwner: 'form'
});
execute({
data: { /* action data */ },
onSuccess: () => console.log('Success!'),
onError: (error) => console.error(error)
});
Ant Design Best Practices
Form Components
import { ConfigurableFormItem } from '@/components/configurableForm/configurableFormItem';
<ConfigurableFormItem model={model}>
<Input placeholder={model.placeholder} />
</ConfigurableFormItem>
Layout & Spacing
// Grid System
<Row gutter={[16, 16]}>
<Col span={12}>Left</Col>
<Col span={12}>Right</Col>
</Row>
// Component Spacing
<Space>
<Button>Cancel</Button>
<Button type="primary">Submit</Button>
</Space>
// Loading States
<Spin spinning={loading}>
{children}
</Spin>
Component Groups
Group | Purpose | Examples |
---|---|---|
Data entry | Input components | TextField, NumberField, Checkbox |
Data display | Information display | Text, Alert, Statistic |
Advanced | Complex components | Autocomplete, RichTextEditor |
Entity references | Entity components | EntityPicker, FileUpload |
Layout | Page structure | Card, Columns, Tabs |
Dev | Development tools | CodeEditor, JsonEditor |
Best Practices
Component Development
✅ Single responsibility
✅ TypeScript interfaces
✅ Responsive design
✅ Meaningful defaults
✅ Performance optimization
✅ Unique IDs using nanoid()
Code Organization
✅ Follow file structure patterns
✅ Reuse utilities
✅ Document complex logic
✅ Use consistent naming
Development Workflow
- Create feature branch
- Implement component
- Register in toolbox
- Test in form designer
- Commit clearly
- Create pull request
Component Lifecycle
Create Component Files
↓
Define Interfaces
↓
Configure Settings
↓
Implement Component
↓
Register in Toolbox
↓
Test in Designer
↓
Deploy & Iterate
Notes on Migrations
Core Principles
- Don’t modify existing migrations once merged into
main
- Add new versions instead of editing old ones
- Changing live migrations risks breaking forms and tests
Safe vs Unsafe to Modify
✅ Safe
- Migrations not merged with main
- Migrations in local/feature branches
❌ Unsafe
- Migrations on main
- Deployed or tested migrations
Proper Migration Strategy
- Always create a new version for behavior changes
- Maintain backward compatibility
- Test before merging
- Use semantic versioning
Example Scenario
Situation: Add a new default setting
✅ Correct: Create a new migration version
❌ Wrong: Modify old migration or just set defaultValue
Resources
Happy Shesha Development!
Start building amazing configurable components today 🚀