Разработка кастомных полей (Fields) в KeystoneJS под ключ

В стандартной поставке KeystoneJS есть все необходимые поля: text, integer, relationship, image. Но рано или поздно упрёшься в ограничения: нужно хранить цвет с прозрачностью, валидировать номер телефона по маске или интегрировать внешний API для автодополнения. Тогда и приходят на помощь кастомные

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Разработка кастомных полей (Fields) в KeystoneJS под ключ
Сложный
~2-3 дня

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1414
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    982
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    994

В стандартной поставке KeystoneJS есть все необходимые поля: text, integer, relationship, image. Но рано или поздно упрёшься в ограничения: нужно хранить цвет с прозрачностью, валидировать номер телефона по маске или интегрировать внешний API для автодополнения. Тогда и приходят на помощь кастомные поля — полноценные расширения, которые включают тип базы данных, GraphQL-резолверы и React-компоненты для Admin UI.

За 5 лет работы с KeystoneJS мы реализовали десятки таких полей — от простых масок до мульти-столбцовых структур. Каждый раз это снижает время разработки на 30–50% по сравнению с костылями в фронтенде. Наша команда имеет 5+ лет опыта в KeystoneJS и реализовала более 20 кастомных полей для различных проектов. Ниже расскажу, из чего состоит кастомное поле и как написать своё.

Задачи, которые решают кастомные поля

  • Нестандартный формат данных. Телефон с маской, цвет с прозрачностью, гео-координаты — стандартные поля не дают такой гибкости. Например, для интернет-магазина нужно хранить цвет товара в hex и отдельно прозрачность. Без кастомного поля пришлось бы создавать два поля и писать валидацию на фронтенде.
  • Сложная валидация. Проверка по внешнему API, cross-field правила (если поле A заполнено, поле B обязательно), уникальность составных ключей. Всё это реализуется через хуки KeystoneJS без дублирования кода на клиенте.
  • Кастомный UI. Автокомплит с внешним источником, визуальный редактор, drag-and-drop — любые интерфейсные задачи, которые не покрываются стандартными полями. React-компоненты позволяют встроить любой UI и легко его тестировать.
  • Производительность. Комбинированные индексы, оптимизированное хранение под частые запросы — кастомное поле даёт полный контроль над схемой БД.

Как создать кастомное поле для валидации телефона?

Кастомное поле в KeystoneJS состоит из трёх слоёв: DB Layer (как данные хранятся в Prisma/БД), GraphQL Layer (типы для чтения/записи через API) и Admin UI Layer (React-компоненты для отображения и редактирования). Рассмотрим на примере поля Phone Number с форматированием.

Поле хранит телефон как строку, но предоставляет UI с маской ввода и валидацию формата. В hooks.validateInput проверяем регулярное выражение, а в resolve для input очищаем строку от лишних символов.

// fields/phoneNumber/index.ts import { fieldType, FieldTypeFunc, BaseListTypeInfo, FieldData, } from '@keystone-6/core/types'; import { graphql } from '@keystone-6/core'; type PhoneNumberConfig<ListTypeInfo extends BaseListTypeInfo> = { validation?: { isRequired?: boolean }; defaultValue?: string; isIndexed?: boolean | 'unique'; db?: { isNullable?: boolean; map?: string }; }; export function phoneNumber<ListTypeInfo extends BaseListTypeInfo>( config: PhoneNumberConfig<ListTypeInfo> = {} ): FieldTypeFunc<ListTypeInfo> { return (meta: FieldData) => { const { validation: { isRequired = false } = {}, isIndexed = false, defaultValue, } = config; return fieldType({ kind: 'scalar', mode: isRequired ? 'required' : 'optional', scalar: 'String', isIndexed, default: defaultValue ? { kind: 'literal', value: defaultValue } : undefined, })({ ...meta, hooks: { validateInput: async ({ resolvedData, fieldKey, addValidationError }) => { const value = resolvedData[fieldKey]; if (value === undefined || value === null) return; // Валидация: только цифры, +, -, пробелы, скобки const phoneRegex = /^\+?[\d\s\-()]{7,20}$/; if (!phoneRegex.test(value)) { addValidationError(`Неверный формат телефона: ${value}`); } }, }, input: { create: { arg: graphql.arg({ type: graphql.String }), resolve: (value) => (value ? normalizePhone(value) : null), }, update: { arg: graphql.arg({ type: graphql.String }), resolve: (value) => (value === undefined ? undefined : value ? normalizePhone(value) : null), }, }, output: graphql.field({ type: graphql.String }), views: require.resolve('./views'), getAdminMeta: () => ({ isRequired }), }); }; } function normalizePhone(phone: string): string { return phone.replace(/\s+/g, '').replace(/[()]/g, ''); } 
// fields/phoneNumber/views.tsx import React, { useState } from 'react'; import { FieldProps, controller } from '@keystone-6/core/fields'; export const Field = ({ field, value, onChange, autoFocus }: FieldProps<typeof controller>) => { const [inputValue, setInputValue] = useState(value || ''); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const raw = e.target.value; setInputValue(raw); onChange?.(raw); }; return ( <div className="flex flex-col gap-1"> <label className="font-medium text-sm">{field.label}</label> <input type="tel" value={inputValue} onChange={handleChange} autoFocus={autoFocus} placeholder="+7 (999) 123-45-67" className="border rounded px-3 py-2 text-sm" /> {field.adminMeta.isRequired && !value && ( <span className="text-red-500 text-xs">Обязательное поле</span> )} </div> ); }; export const Cell = ({ item, field }) => ( <span>{item[field.path] || '—'}</span> ); export const CardValue = ({ item, field }) => ( <span>{item[field.path] || 'Не указан'}</span> ); export const controller = (config) => ({ path: config.path, label: config.label, description: config.description, adminMeta: config.fieldMeta, graphqlSelection: config.path, defaultValue: '', deserialize: (data) => data[config.path] ?? '', serialize: (value) => ({ [config.path]: value || null }), validate: (value) => { if (config.fieldMeta.isRequired && !value) return false; return true; }, }); 

Использование в списке:

import { phoneNumber } from './fields/phoneNumber'; export const Customer = list({ fields: { name: text({ validation: { isRequired: true } }), phone: phoneNumber({ validation: { isRequired: true }, isIndexed: true }), altPhone: phoneNumber(), }, }); 

«Кастомные поля — ключевой элемент гибкой CMS», — отмечает команда KeystoneJS в документации.

Почему KeystoneJS лучше Strapi для нестандартных полей?

KeystoneJS выигрывает в гибкости: вы определяете полный стек — от схемы БД до React-компонентов — без ограничений. Strapi удобен для быстрых решений, но кастомизация там сводится к замене частей кода, а не к созданию модульного расширения. KeystoneJS лучше подходит для проектов, где требуется нестандартная логика или уникальный UI. Средняя экономия времени на разработку функционала с помощью кастомных полей составляет 40%.

Процесс разработки и сроки

  1. Анализ требований — определяем формат данных, валидацию, UI, необходимые фильтры.
  2. Проектирование схемы — выбираем тип поля (scalar/multi), проектируем Prisma-модель.
  3. Разработка — пишем GraphQL-резолверы, React-компоненты, хуки.
  4. Тестирование — юнит-тесты на валидацию и трансформацию, интеграционные на работу в контексте списка.
  5. Интеграция и деплой — подключаем поле к проекту, проверяем в Admin UI.
Тип поля Время
Простое поле (один столбец, кастомный UI) 1–2 дня
Поле с несколькими столбцами 2–3 дня
Поле с внешними API (Mapbox, Unsplash picker) 3–5 дней
Поле с фильтрами и сортировкой +0.5–1 день

Публикация как npm-пакета для переиспользования между проектами добавляет 0.5–1 день на настройку сборки и документацию.

Что входит в работу

Результат Описание
Исходный код поля TypeScript-модуль с полным набором файлов (index, views, controller)
Документация API-документация и примеры использования в вашем проекте
Тесты Юнит-тесты на валидацию, хуки и GraphQL-слой
Интеграция Подключение поля к вашей схеме и настройка Admin UI
Поддержка после запуска 2 недели бесплатной поддержки по устранению возможных проблем

Готовы обсудить ваше кастомное поле? Свяжитесь с нами — мы бесплатно оценим задачу и предложим решение. Обращайтесь за бесплатной консультацией.