ConvertKit - v1.0.0
Livewire Subscribe Form
Server-driven subscribe form using Livewire 3.
Two flavors:
- Direct — call the Kit API from the Livewire component itself. Simpler; no queue involvement.
- Via the public endpoint — dispatch the queued job via
POST /convertkit/subscribers. Better under load.
Direct
Bypasses the REST endpoint entirely. Straight into the API client.
namespace App\Livewire;
use Livewire\Attributes\Validate;
use Livewire\Component;
class SubscribeForm extends Component
{
#[Validate( 'required|email' )]
public string $email = '';
public bool $done = false;
public function submit(): void
{
$this->validate();
convertkit()->forms()->subscribe(
formId: 12345,
email: $this->email,
tags: [ 100 ],
);
$this->done = true;
}
public function render(): string
{
return <<<'BLADE'
<div>
@if ( $done )
<p class="text-success">Thanks — check your inbox to confirm.</p>
@else
<form wire:submit="submit" class="flex gap-2">
<input
type="email"
wire:model="email"
placeholder="you@example.com"
class="input input-bordered flex-1"
required
/>
<button
type="submit"
class="btn btn-primary"
wire:loading.attr="disabled"
>
<span wire:loading.remove>Subscribe</span>
<span wire:loading>Sending…</span>
</button>
</form>
@error( 'email' )
<p class="mt-2 text-error">{{ $message }}</p>
@enderror
@endif
</div>
BLADE;
}
}
Drop it in a Blade view:
<livewire:subscribe-form />
Trade-off. The API call runs inside the HTTP request. If Kit is slow, your form is slow. Fine for low-volume forms; not great for a homepage hero.
Via the public endpoint (queued)
Same UX, but POSTs to /convertkit/subscribers so the Kit call runs on the queue.
namespace App\Livewire;
use Illuminate\Support\Facades\Http;
use Livewire\Attributes\Validate;
use Livewire\Component;
class SubscribeForm extends Component
{
#[Validate( 'required|email' )]
public string $email = '';
public bool $done = false;
public ?string $error = null;
public function submit(): void
{
$this->validate();
$this->error = null;
$response = Http::acceptJson()
->asJson()
->post( url( '/convertkit/subscribers' ), [
'feed_id' => 1,
'email' => $this->email,
] );
if ( 202 === $response->status() ) {
$this->done = true;
return;
}
if ( 429 === $response->status() ) {
$this->error = 'Too many attempts — please try again in a minute.';
return;
}
$this->error = $response->json( 'message', 'Something went wrong. Try again shortly.' );
}
public function render(): string
{
return <<<'BLADE'
<div>
@if ( $done )
<p class="text-success">Thanks — check your inbox to confirm.</p>
@else
<form wire:submit="submit" class="flex gap-2">
<input
type="email"
wire:model="email"
placeholder="you@example.com"
class="input input-bordered flex-1"
required
/>
<button
type="submit"
class="btn btn-primary"
wire:loading.attr="disabled"
>
<span wire:loading.remove>Subscribe</span>
<span wire:loading>Sending…</span>
</button>
</form>
@if ( $error )
<p class="mt-2 text-error">{{ $error }}</p>
@endif
@error( 'email' )
<p class="mt-2 text-error">{{ $message }}</p>
@enderror
@endif
</div>
BLADE;
}
}
Since Livewire runs server-side, CSRF and session cookies are handled transparently — no manual token wiring.
With artisanpack-ui/livewire-ui-components
Swap the raw markup for the shipped components:
<div>
@if ( $done )
<x-artisanpack-alert type="success">
Thanks — check your inbox to confirm.
</x-artisanpack-alert>
@else
<form wire:submit="submit" class="flex gap-2">
<x-artisanpack-input
type="email"
wire:model="email"
placeholder="you@example.com"
:error="$errors->first('email')"
required
/>
<x-artisanpack-button color="primary" type="submit" wire:loading.attr="disabled">
Subscribe
</x-artisanpack-button>
</form>
@endif
</div>
Testing the component
Use FakeConvertKit plus Livewire's testing helpers.
use ArtisanPackUI\ConvertKit\Facades\ConvertKit;
use App\Livewire\SubscribeForm;
use Livewire\Livewire;
it( 'subscribes via the API client', function (): void {
$fake = ConvertKit::fake();
Livewire::test( SubscribeForm::class )
->set( 'email', 'jane@example.com' )
->call( 'submit' )
->assertSet( 'done', true );
$fake->assertSubscribed( 'jane@example.com', 12345 );
} );
For the queued variant, fake the HTTP client:
use Illuminate\Support\Facades\Http;
Http::fake( [ 'convertkit/subscribers' => Http::response( [ 'message' => 'Subscribe queued.' ], 202 ) ] );