Para devs e CTOs
Ele escreve o CRUD. Você escreve o resto.
Tabelas, telas e regras montadas no visual, no código ou pelo agente. Sai Eloquent, Blade e Alpine num projeto Laravel padrão, que você revisa, versiona e mantém.
Leia o que ele gera antes de confiar.
Um formulário, os três arquivos que ele produz. Passe o mouse num campo e veja as linhas dele no Blade, no model e no controller.
resources/views/venda-form.blade.php
<mad-form submit="onSave" data-mad-entity-id="693"><mad-form-section title="Cadastro de Venda" icon="file-text"><mad-form-grid :cols="2"><mad-dbcombo-field data-mad-column-id="4533" data-mad-entity-id="693" data-mad-ref-entity-id="691" data-mad-ref-column-id="4521" data-mad-ref-display-column-id="4522" name="pessoa_id" label="Pessoa" database="app" model="Pessoa" key="id" display="{nome}" order-by="nome" order="asc" :required="true" /><mad-date-field data-mad-column-id="4534" data-mad-entity-id="693" name="data" label="Data" :required="true" /></mad-form-grid><mad-form-grid :cols="2"><mad-money-field name="total" label="Total" prefix="R$" :decimals="2" :readonly="true" /><mad-input-field data-mad-column-id="4536" data-mad-entity-id="693" name="status" label="Status" /></mad-form-grid></mad-form-section><mad-form-actions><mad-btn variant="primary" type="submit" icon="save">Salvar</mad-btn></mad-form-actions></mad-form> app/Models/App/Venda.php
<?phpnamespace App\Models\App;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo;use Illuminate\Database\Eloquent\Relations\HasMany;use Mad\Database\Concerns\HasIdPolicy;use Mad\Database\Concerns\HasMadAudit;class Venda extends Model{use HasIdPolicy, HasMadAudit;protected $connection = 'app';protected $table = 'venda';// Colunas de auditoria/soft-delete desta tabela (chave ausente = desligada).protected array $madAudit = ['created_at' => 'created_at','updated_at' => 'updated_at',];protected $fillable = ['pessoa_id','data','total','status',];public static function rules($id = null): array{return ['pessoa_id' => 'required|integer','data' => 'required','total' => 'nullable|numeric','status' => 'nullable|max:20',];}public function pessoa(): BelongsTo{return $this->belongsTo(Pessoa::class, 'pessoa_id');}public function vendaItems(): HasMany{return $this->hasMany(VendaItem::class, 'venda_id');}protected static function booted(): void{static::deleting(function (self $model) {if (VendaItem::where('venda_id', '=', $model->id)->exists()) {throw new \Exception('Não é possível deletar este registro pois ele está sendo utilizado em outra parte do sistema');}});}} app/Control/VendaForm.php
<?phpuse Mad\Component\MadComponent;use Mad\Form\MadForm;use Mad\Form\MadValidationException;use Mad\Http\MadResponse;use Mad\Ui\MadMessage;use App\Models\App\Venda;use Illuminate\Support\Facades\DB;// use App\Control\VendaList;// @mad-block:user:uses {// @mad-block:user:uses }/*** @mad-generated* @mad-entity-id 693*/class VendaForm extends MadComponent{// @mad-block:config:properties {protected static string $wrapper = self::DRAWER;protected static string $title = 'Cadastro de Venda';protected static string $size = '600px';protected static string $side = 'right';public MadForm $form;// int|string: tabela cuja PK é varchar/char (código, matrícula) tem chave// de texto — `int` transformaria 'ABC-1' em 0 e salvaria no registro errado.public int|string|null $recordId = null;// @mad-block:config:properties }// @mad-block:user:properties {// @mad-block:user:properties }// @mad-block:lifecycle:mount {public function mount(array $params = []): void{$this->form = new MadForm('form');if (! empty($params['id'])) {$this->onEdit($params['id']);}}// @mad-block:lifecycle:mount }// @mad-block:lifecycle:onEdit {public function onEdit(int|string $id): void{// @mad-block:locked:onEdit:master {$venda = Venda::findOrFail($id);$this->recordId = $venda->getKey();// @mad-block:locked:onEdit:master }// @mad-block:detail-load-hooks {// @mad-block:detail-load-hooks }$this->form->fill($venda);}// @mad-block:lifecycle:onEdit }// @mad-block:lifecycle:onSave {public function onSave(): MadResponse{try {$this->form->validate(Venda::rules($this->recordId));$venda = DB::connection('app')->transaction(function () {// @mad-block:locked:onSave:master {$venda = Venda::findOrNew($this->recordId);// @mad-block:locked:onSave:master }// @mad-block:detail-save-hooks {// @mad-block:detail-save-hooks }$this->form->save($venda);$this->recordId = $venda->getKey();return $venda;});// @mad-block:combo-origin {// Tela aberta por um combo ("Sem resultados → Cadastrar novo"):// devolve a option nova selecionada e fecha o overlay. Fora desse// caso returnToCombo() responde null e o fluxo normal segue.if (($__combo = $this->returnToCombo($this->recordId)) !== null) {return $__combo;}// @mad-block:combo-origin }// @mad-block:after-save {return (new MadResponse())->toast('Registro salvo com sucesso!', 'success')->closeDrawer()->manageRow($this->recordId, VendaList::class);// @mad-block:after-save }} catch (MadValidationException $e) {return $e->asInline();} catch (\Throwable $e) {return MadMessage::error('Erro ao salvar', $e->getMessage());}}// @mad-block:lifecycle:onSave }// Caminho da view gerado pela plataforma a partir da PASTA DO MÓDULO.// Bloco byte-exact: NÃO edite — trocar o módulo regenera este path sozinho.// @mad-block:locked:view-name {protected function viewName(): string{return 'venda-form';}// @mad-block:locked:view-name }// @mad-block:lifecycle:view {protected function view(): string|array{$replaces = [];return [$this->viewName(), $replaces];}// @mad-block:lifecycle:view }// @mad-block:user:functions {// @mad-block:user:functions }} Seu código sobrevive à próxima geração.
O controller gerado é dividido em blocos. Os que a interface controla ficam travados. Os @mad-block:user são seus: quando a tela muda e o arquivo é gerado de novo, eles ficam como você deixou.
- Imports, propriedades e funções suas em blocos próprios
- Validação pelo
rules()do model, salvamento em transação - Um componente PHP que você lê de cima a baixo, sobre o MadComponent do framework
<?phpuse Mad\Component\MadComponent;use Mad\Form\MadForm;use Mad\Form\MadValidationException;use Mad\Http\MadResponse;use Mad\Ui\MadMessage;use App\Models\App\Venda;use Illuminate\Support\Facades\DB;// use App\Control\VendaList;// @mad-block:user:uses {// @mad-block:user:uses }/*** @mad-generated* @mad-entity-id 693*/class VendaForm extends MadComponent{// @mad-block:config:properties {protected static string $wrapper = self::DRAWER;protected static string $title = 'Cadastro de Venda';protected static string $size = '600px';protected static string $side = 'right';public MadForm $form;// int|string: tabela cuja PK é varchar/char (código, matrícula) tem chave// de texto — `int` transformaria 'ABC-1' em 0 e salvaria no registro errado.public int|string|null $recordId = null;// @mad-block:config:properties }// @mad-block:user:properties {// @mad-block:user:properties }// @mad-block:lifecycle:mount {public function mount(array $params = []): void{$this->form = new MadForm('form');if (! empty($params['id'])) {$this->onEdit($params['id']);}}// @mad-block:lifecycle:mount }// @mad-block:lifecycle:onEdit {public function onEdit(int|string $id): void{// @mad-block:locked:onEdit:master {$venda = Venda::findOrFail($id);$this->recordId = $venda->getKey();// @mad-block:locked:onEdit:master }// @mad-block:detail-load-hooks {// @mad-block:detail-load-hooks }$this->form->fill($venda);}// @mad-block:lifecycle:onEdit }// @mad-block:lifecycle:onSave {public function onSave(): MadResponse{try {$this->form->validate(Venda::rules($this->recordId));$venda = DB::connection('app')->transaction(function () {// @mad-block:locked:onSave:master {$venda = Venda::findOrNew($this->recordId);// @mad-block:locked:onSave:master }// @mad-block:detail-save-hooks {// @mad-block:detail-save-hooks }$this->form->save($venda);$this->recordId = $venda->getKey();return $venda;});// @mad-block:combo-origin {// Tela aberta por um combo ("Sem resultados → Cadastrar novo"):// devolve a option nova selecionada e fecha o overlay. Fora desse// caso returnToCombo() responde null e o fluxo normal segue.if (($__combo = $this->returnToCombo($this->recordId)) !== null) {return $__combo;}// @mad-block:combo-origin }// @mad-block:after-save {return (new MadResponse())->toast('Registro salvo com sucesso!', 'success')->closeDrawer()->manageRow($this->recordId, VendaList::class);// @mad-block:after-save }} catch (MadValidationException $e) {return $e->asInline();} catch (\Throwable $e) {return MadMessage::error('Erro ao salvar', $e->getMessage());}}// @mad-block:lifecycle:onSave }// Caminho da view gerado pela plataforma a partir da PASTA DO MÓDULO.// Bloco byte-exact: NÃO edite — trocar o módulo regenera este path sozinho.// @mad-block:locked:view-name {protected function viewName(): string{return 'venda-form';}// @mad-block:locked:view-name }// @mad-block:lifecycle:view {protected function view(): string|array{$replaces = [];return [$this->viewName(), $replaces];}// @mad-block:lifecycle:view }// @mad-block:user:functions {// @mad-block:user:functions }} Traga o seu agente. Ou use o nosso.
O agente do MadBuilder lê o banco, cria tabelas e gera telas no mesmo projeto que você edita. Ele escreve a tag do componente MAD, não o HTML, o CSS e o JS por trás dela, por isso gasta de 65% a 75% menos tokens.
$ claude mcp add madbuilder --token mgw_•••• --project 1042 - Claude Code, Cursor ou qualquer cliente MCP
- Token exclusivo por projeto, com revogação na hora
- Permissão por ferramenta e auditoria de cada chamada
- Sua chave da Anthropic, OpenRouter, xAI, Z.AI ou Ark, ou o Mad Coding Plan
Você 14:05
Crie um CRUD de venda com itens (mestre-detalhe): venda tem cliente, data e valor total; cada item tem produto, quantidade e valor unitário. Gere a listagem e o formulário.
Agente 14:08
- Vou criar o CRUD mestre-detalhe de venda. Primeiro preciso verificar o banco de dados e criar as tabelas que ainda não existem.
mad_db_inspect list_tables17 tabelas no modelo, nenhuma de venda- Preciso criar produto, venda e venda_item. Deixarei pessoa como cliente.
mad_db_table create produtonome, preco_venda, unidade, ativo e o model Produtomad_db_table create vendacliente_id, data_venda, valor_total e o model Vendamad_db_table create venda_itemvenda_id, produto_id, quantidade, valor_unitario, valor_total e o model VendaItem- Tabelas criadas. Agora vou carregar a skill do framework MAD e gerar o CRUD mestre-detalhe.
skill mad-frameworkskill carregadacrud_generate venda + detalhe venda_item2 telas criadas: VendaList e VendaForm
Sem motor proprietário no meio.
O que roda em produção é o seu projeto Laravel. O MadBuilder fica no seu fluxo de trabalho, não no seu servidor.
Laravel, Eloquent, Blade e Alpine
O app gerado declara laravel/framework ^13.8 e o framework MAD vem dentro do projeto.
Seis bancos
MySQL ou MariaDB, PostgreSQL, SQLite, Oracle, SQL Server e Firebird.
SSH, Git ou ZIP
O deploy por SSH guarda os últimos 5 backups. O Git vai pro GitHub, GitLab ou Bitbucket.
Ambiente de teste num clique
PHP 8.4 ou 8.5, com PostgreSQL, MySQL, MariaDB ou SQLite, isolado por projeto.
Erros e APM embutidos
Rastreamento opt-in, sem contratar Sentry ou New Relic à parte.
Rotas limpas
Uma URL por tela, com unicidade validada e rotas reservadas protegidas.
Perguntas de quem vai manter o código
E se eu quiser sair do MadBuilder?
Baixe o ZIP ou faça push pro seu Git. É um projeto Laravel comum, com o framework MAD dentro da pasta packages. Roda sem o MadBuilder e qualquer dev Laravel mantém.
O que acontece com o código que eu escrevo à mão?
Ele fica dentro dos blocos @mad-block:user. Quando alguém muda a tela e o arquivo é gerado de novo, esses blocos são preservados. As partes que a interface controla ficam travadas e marcadas no editor.
Posso editar direto no PHP e no Blade?
Pode. Visual, PHP e Blade são três vistas do mesmo arquivo. O que você muda numa aparece nas outras.
Dá pra versionar no Git?
Sim. O Git Deploy faz push pro GitHub, GitLab ou Bitbucket, e você escolhe as pastas que não sobem, como vendor e storage.
Quais bancos são suportados?
MySQL ou MariaDB, PostgreSQL, SQLite, Oracle, SQL Server e Firebird.
Posso usar o meu agente de IA em vez do de vocês?
Pode. O projeto expõe um servidor MCP: o Claude Code, o Cursor ou qualquer cliente MCP cria telas, modela dados e publica, com um token por projeto e cada chamada registrada.
Baixe o .zip antes de marcar reunião.
Crie a conta, monte uma tela e abra o projeto no seu editor. Se o código não passar na revisão do seu time, você perdeu uma tarde.
- Crie a contaBeta aberto, sem cartão.
- Monte uma telaNo visual, no código ou pedindo à IA.
- Baixe o .zipUm projeto Laravel que roda onde você quiser.