Formularze i Form Builder

Twórz formularze w wizualnym Form Builderze i podłączaj je do custom bloków zapisując ID formularza w polu bloku.

31 marca 2026

Przegląd

Cmssy posiada wbudowany Form Builder, który pozwala tworzyć formularze wizualnie i podłączać je do dowolnego custom bloku. Formularze obsługują walidację, zgłoszenia, powiadomienia email i zdarzenia dostarczane webhookami — bloki muszą tylko renderować UI.

System składa się z dwóch części:

  1. Form Builder (Dashboard > Formularze) — definiowanie pól, walidacji i akcji
  2. Pole formId na bloku — przechowuje, który formularz blok ma renderować

Tworzenie formularza

1. Otwórz Form Builder

Przejdź do Dashboard → Formularze → Utwórz formularz. Nadaj nazwę i slug (np. formularz-kontaktowy).

2. Dodaj pola

Każde pole posiada:

WłaściwośćOpis
nameUnikalny klucz pola (np. email, wiadomosc)
typetext, email, textarea, number, phone, url, date, select, multiselect, checkbox, radio, file, hidden
labelEtykieta wyświetlana (wielojęzyczna)
placeholderTekst zastępczy (wielojęzyczny)
validationrequired, minLength, maxLength, minValue, maxValue, pattern, customMessage
widthfull, half lub third — kontroluje szerokość pola w siatce
optionsDla select, multiselect, radio — tablica { value, label }
orderKolejność wyświetlania

3. Skonfiguruj ustawienia

UstawienieOpis
Typ akcjicontact albo custom — nic innego nie jest przyjmowane. Żaden nie zmienia tego, co serwer robi ze zgłoszeniem; to etykieta, po której Twój blok może wybrać layout.
Odbiorcy emailLista emaili otrzymujących powiadomienia
Wiadomość sukcesuWyświetlana po udanym wysłaniu (wielojęzyczna)
Wiadomość błęduWyświetlana przy niepowodzeniu (wielojęzyczna)
Tekst przyciskuTekst na przycisku submit (wielojęzyczny)
Zapisuj zgłoszeniaPrzechowuj zgłoszenia w bazie (domyślnie: true)
Wysyłaj powiadomieniaEmail do odbiorców przy każdym zgłoszeniu (domyślnie: true)
Wymagaj logowaniaTylko zalogowani użytkownicy mogą wysłać

4. Opublikuj

Ustaw status na Opublikowany. Skopiuj jego ID formularza — będziesz się do niego odwoływać z bloku.

Używanie formularzy w custom blokach

Odwołanie do formularza

Dodaj pole formId do schematu bloku. Użyj fields.form(): renderuje w edytorze picker formularzy i zapisuje id wybranego formularza, więc nikt nie musi przeklejać id ręcznie.

// blocks/contact/block.ts
import { defineBlock, fields } from "@cmssy/react";
import Contact from "./Contact";

export const contactBlock = defineBlock({
  type: "contact",
  label: "Contact",
  component: Contact,
  props: {
    formId: fields.form({ label: "Formularz" }),
    submitLoadingText: fields.text({ label: "Tekst ładowania", defaultValue: "Wysyłanie..." }),
    successHeading: fields.text({ label: "Nagłówek sukcesu", defaultValue: "Wiadomość wysłana!" }),
  },
});

Renderowanie formularza

SDK automatycznie pobiera każdy formularz wskazany przez formId bloku i wstrzykuje jego definicję do context bloku jako context.forms[formId] — bez ręcznego pobierania:

// blocks/contact/src/Contact.tsx
export default function Contact({ content, context }) {
  const { formId } = content;
  const formDef = formId ? context?.forms?.[formId] ?? null : null;
  // formDef.fields   - tablica definicji pól
  // formDef.settings - tekst przycisku, wiadomości, typ akcji
  // ...wyrenderuj pola
}

Wysyłanie formularza

Wysyłka idzie przez public.form.submit. Trzymaj mutację we własnym repo i wysyłaj ją swoim skonfigurowanym klientem - SDK niesie ten sam string jako SUBMIT_FORM_MUTATION, ale pod @cmssy/core/internal, które nie jest publicznym API. Backend waliduje pola, zapisuje zgłoszenie, wysyła maile z powiadomieniami, emituje form.submitted i odpowiada wynikiem sukces/błąd:

// blocks/contact/actions.ts
"use server";
import { createCmssyClient, type CmssyFormSubmitResponse } from "@cmssy/react";
import { cmssy } from "@/cmssy.config";

const SUBMIT_FORM = `mutation SubmitForm($formId: ID!, $input: SubmitFormInput!) {
  public {
    form {
      submit(formId: $formId, input: $input) {
        success
        message
        submissionId
        redirectUrl
      }
    }
  }
}`;

const client = createCmssyClient(cmssy);

export async function submitForm(formId: string, data: Record<string, string>) {
  const res = await client.query<{
    public: { form: { submit: CmssyFormSubmitResponse } };
  }>(SUBMIT_FORM, { formId, input: { data } });

  return res.public.form.submit; // { success, message, submissionId, redirectUrl }
}

Wysłanie formularza to jedyny zapis, jaki publiczny frontend może wykonać bez tokenu. Wszystko inne wokół formularzy - tworzenie ich, czytanie zgłoszeń, zmiana statusu - wymaga autoryzowanego klienta.

Referencyjne bloki formularzy

Cmssy nie dostarcza bloków — w modelu headless bloki budujesz w swoim repozytorium za pomocą SDK. To typowe bloki formularzy, które możesz zaimplementować jako wzorce referencyjne, każdy podłącza formularz z Form Buildera (jego actionType) do bloku renderowanego przez Ciebie:

BlokTyp akcjiOpis
ContactcontactFormularz kontaktowy z kartami info i cytatem
NewslettercustomZapis na maila, który webhook workspace'u przekazuje Twojemu dostawcy mailingu
Zapytanie ofertowecontactDłuższe zapytanie, które ląduje w Zgłoszeniach i idzie mailem

Każdy definiuje formularz po stronie serwera w Form Builderze i renderuje go w bloku przez pole formId — markup należy do dewelopera. Zobacz Uwierzytelnianie członków po flow auth.

Logowanie, rejestracja i reset hasła to nie formularze z Form Buildera: wołają bezpośrednio mutacje auth członków (login, register, forgotPassword, resetPassword).

Zgłoszenia formularzy

Przeglądanie zgłoszeń

Przejdź do Dashboard → Formularze → [Twój formularz] → Zgłoszenia. Każde zgłoszenie pokazuje:

  • Wszystkie wartości pól
  • Status (pending, processed, spam, archived)
  • Adres IP, user agent, referrer
  • Znacznik czasu
  • Status dostarczenia email

Cykl życia zgłoszenia

  1. Użytkownik wysyła formularz na Twojej opublikowanej stronie
  2. Serwer waliduje pola i sprawdza limity częstotliwości
  3. Zgłoszenie zapisane ze statusem pending
  4. Powiadomienie email wysłane do odbiorców (jeśli włączone)
  5. form.submitted wyemitowane — już po zapisie zgłoszenia, do każdego webhooka workspace'u zapisanego na to zdarzenie, a nieudana dostawa nigdy nie psuje zgłoszenia
  6. Status zostaje pending, dopóki nie zmienisz go w zakładce Zgłoszenia

Jak zgłoszenie trafia do zewnętrznego systemu

Zarejestruj webhook workspace'u na zdarzenie form.submitted. To jedyna droga na zewnątrz i jest trwała: podpisana, z ponowieniami, pozbierana przez sweeper i widoczna w logu dostaw, który możesz przeczytać i odtworzyć. Formularz nie ma własnego ustawienia webhooka.

Zdarzenie niesie id zgłoszenia, nie odpowiedzi. Odczytasz je przez get_form_submission albo form.submission(submissionId:) w API, na tej samej permisji forms:submissions:view, która pozwala endpointowi zapisać się na to zdarzenie. Odpowiedzi zostają wtedy w jednym miejscu z jedną ścieżką usunięcia, a żadne duże zgłoszenie nie przepchnie zdarzenia przez limit rozmiaru dostawy i nie uciszy go wszystkim pozostałym odbiorcom.

Ochrona przed spamem

  • Limit per-IP na formularz
  • Pola honeypot można dodać jako pole typu hidden

Typy akcji - referencja

contact

Zapisuje zgłoszenie + wysyła email do skonfigurowanych odbiorców. Domyślny dla większości formularzy.

custom

Etykieta i nic więcej: serwer traktuje formularz custom dokładnie jak contact. Używaj jej, żeby oznaczyć formularze, które Twój blok renderuje inaczej. Żeby dotrzeć do zewnętrznego serwisu (Zapier, Slack, CRM), zapisz webhook workspace'u na form.submitted.

Logowanie i rejestracja członków to nie typy akcji formularza — zobacz Uwierzytelnianie członków.

Wskazówki

  • Zawsze opublikuj formularz zanim się do niego odwołasz — formularze draft nie mogą być pobrane
  • Używaj szerokości half i third aby tworzyć wielokolumnowe układy (np. imię + nazwisko obok siebie)
  • Wielojęzyczne etykiety — pola formularza wspierają etykiety i placeholdery per-język
  • Testowe zgłoszenia są zapisywane jak prawdziwe — usuń je z zakładki Zgłoszenia po zakończeniu
  • Debugowanie webhooków — otwórz Ustawienia → Webhooki → Dostawy: każda próba jest tam z kodem odpowiedzi, jedną możesz odtworzyć