CustomizeTypeScript SDKReactextension
Custom question types and renderers
Register one metadata-driven model and render it through a cloned public renderer registry.
Register metadata first
A custom question begins as a registry class with a parent, factory, properties, and any child collections. That one registration drives parsing, serialization, schema generation, and the Creator property grid.
import { MetadataRegistry, Question, registerBuiltInTypes } from '@kajay/core';
import {
defaultPageElementRenderers,
QuestionErrors,
QuestionTitleContent,
questionErrorId,
questionId,
useIdScope,
useQuestionValue,
} from '@kajay/react';
class MoodQuestion extends Question {
override get type(): string { return 'mood'; }
}
const registry = new MetadataRegistry();
registerBuiltInTypes(registry);
registry.addClass({
name: 'mood',
parent: 'question',
create: () => new MoodQuestion(),
properties: [{ name: 'scale', type: 'number', defaultValue: 5 }],
});
const renderers = defaultPageElementRenderers.clone();
renderers.registerQuestion('mood', function MoodRenderer({ survey, question }) {
const scope = useIdScope();
const value = useQuestionValue(survey, question);
const id = questionId(question, scope);
const errors = questionErrorId(question, scope);
return (
<section>
<label htmlFor={id}><QuestionTitleContent question={question} /></label>
<QuestionErrors survey={survey} question={question} at="top" id={errors} />
<input
id={id}
type="range"
value={Number(value ?? 0)}
aria-describedby={question.hasErrors ? errors : undefined}
onChange={(event) => { question.value = Number(event.target.value); }}
/>
<QuestionErrors survey={survey} question={question} at="bottom" id={errors} />
</section>
);
});
const { survey } = parseSurvey(definition, { registry });
<Survey model={survey} renderers={renderers} />Clone the renderer registry
Clone defaultPageElementRenderers for one consumer and register the new renderer there. Do not mutate global defaults or import renderer internals.
Add properties to existing types
Use addProperty when every question or one existing class needs host metadata. Pass the same registry to runtime and Creator so parsing, serialization, and authoring agree.
import {
MetadataRegistry,
parseSurvey,
registerBuiltInTypes,
} from '@kajay/core';
import { CreatorWorkspace } from '@kajay/creator-core';
const registry = new MetadataRegistry();
registerBuiltInTypes(registry);
registry.addProperty('question', {
name: 'helpUrl',
type: 'string',
description: 'A link to guidance for this question.',
});
// Use this same registry everywhere the definition travels.
const { survey } = parseSurvey(definition, { registry });
const workspace = new CreatorWorkspace({ definition, registry });Render the question contract
- Use exported title, error, id-scope, and component helpers rather than inventing parallel semantics.
- Write through the question model so events, logic, read-only state, and validation remain coherent.
- Add contract, unit, and real-browser proofs for new observable behavior.