hook_webform_submission_insert: A Practical Drupal Tutorial
If you've ever needed to react immediately after a webform submission is saved — trigger an
external API, write to a custom table, send a tailored notification — hook_webform_submission_insert
is the hook you want. This post covers what it does, when it fires, and three real-world
patterns you can drop into your custom module today.
What is hook_webform_submission_insert?
hook_webform_submission_insert is a Drupal hook provided by the
Webform module.
It fires once, after a new submission entity has been persisted to the database
for the first time. At this point the submission has a valid ID, all field values are finalized,
and you are free to read or react to them — but not to alter the saved data (for that, use
hook_webform_submission_presave).
_insert fires only on creation.
For updates use hook_webform_submission_update.
For both, use hook_webform_submission_postsave.
Hook Signature
Place this in your custom module's .module file (or a relevant .inc):
<?php
use Drupal\webform\WebformSubmissionInterface;
/**
* Implements hook_webform_submission_insert().
*
* Fires after a new webform submission is saved for the first time.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* The webform submission entity that was just created.
*/
function mymodule_webform_submission_insert(WebformSubmissionInterface $webform_submission): void {
// Your logic here.
}
Reading Submission Data
The most common first step: read the submitted values.
<?php
function mymodule_webform_submission_insert(WebformSubmissionInterface $webform_submission): void {
// Get the webform ID to act only on a specific form.
$webform_id = $webform_submission->getWebform()->id();
if ($webform_id !== 'contact_form') {
return;
}
// Read individual field values.
$data = $webform_submission->getData();
$name = $data['full_name'] ?? '';
$email = $data['email'] ?? '';
$message = $data['message'] ?? '';
// Read submission metadata.
$sid = $webform_submission->id();
$uid = $webform_submission->getOwnerId();
$time = $webform_submission->getCreatedTime(); // Unix timestamp.
}
Example 1 — Send a Custom Slack Notification
A common pattern: ping a Slack channel whenever a high-priority contact form is submitted, without relying on the Webform module's built-in email handler.
<?php
function mymodule_webform_submission_insert(WebformSubmissionInterface $webform_submission): void {
if ($webform_submission->getWebform()->id() !== 'urgent_request') {
return;
}
$data = $webform_submission->getData();
$subject = $data['subject'] ?? '(no subject)';
$from = $data['email'] ?? 'unknown';
$payload = json_encode([
'text' => "New urgent request from {$from}: {$subject}",
]);
/** @var \GuzzleHttp\ClientInterface $http */
$http = \Drupal::httpClient();
try {
$http->post('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', [
'body' => $payload,
'headers' => ['Content-Type' => 'application/json'],
]);
}
catch (\Exception $e) {
\Drupal::logger('mymodule')->error('Slack notification failed: @msg', [
'@msg' => $e->getMessage(),
]);
}
}
Example 2 — Write to a Custom Database Table
Sometimes you need data in your own schema — for reporting, external syncs, or avoiding Webform's own query layer.
<?php
function mymodule_webform_submission_insert(WebformSubmissionInterface $webform_submission): void {
if ($webform_submission->getWebform()->id() !== 'newsletter_signup') {
return;
}
$data = $webform_submission->getData();
$email = $data['email'] ?? '';
if (empty($email)) {
return;
}
\Drupal::database()->insert('mymodule_newsletter_queue')
->fields([
'email' => $email,
'sid' => $webform_submission->id(),
'created' => \Drupal::time()->getRequestTime(),
'status' => 'pending',
])
->execute();
}
Example 3 — Create a Related Entity on Submission
This is where it gets powerful. Automatically create a node, a custom entity, or a flag the moment someone submits a form.
<?php
use Drupal\node\Entity\Node;
function mymodule_webform_submission_insert(WebformSubmissionInterface $webform_submission): void {
if ($webform_submission->getWebform()->id() !== 'project_request') {
return;
}
$data = $webform_submission->getData();
$title = $data['project_name'] ?? 'Untitled project';
$node = Node::create([
'type' => 'project',
'title' => $title,
'field_requestor_sid' => $webform_submission->id(),
'status' => 0, // Draft until reviewed.
'uid' => $webform_submission->getOwnerId(),
]);
$node->save();
}
\Drupal::queue('mymodule_queue')->createItem($data)
and process it with a worker. This keeps form submission fast and resilient.
Common Gotchas
-
Draft submissions:
hook_webform_submission_insertfires for draft saves too. Check$webform_submission->isDraft()if you only want to act on final submissions. -
Anonymous submissions:
getOwnerId()returns0for anonymous users. Guard accordingly. - Bulk imports: if submissions are imported via Drush or migrations, this hook fires for each one. If that's a problem, check the source or add a flag field to skip processing.
-
Don't re-save the submission here. Calling
$webform_submission->save()insidehook_webform_submission_inserttriggers another insert cycle. Usehook_webform_submission_presaveif you need to modify data before it hits the DB.
Related Hooks at a Glance
| Hook | When it fires | Can modify data? |
|---|---|---|
hook_webform_submission_presave |
Before any save (insert or update) | Yes |
hook_webform_submission_insert |
After first save only | No |
hook_webform_submission_update |
After subsequent saves | No |
hook_webform_submission_postsave |
After any save (insert or update) | No |
Conclusion
hook_webform_submission_insert is the cleanest place to react to a brand-new
webform submission: the data is committed, the ID exists, and you're not blocking the
save transaction. Keep the hook lean — offload anything slow to a queue — and scope it to
the webform IDs you actually care about.
Have a tricky Drupal hook scenario you're wrestling with? I've also written about AI-generated code colliding with legacy Drupal and hooks firing at the wrong time in complex form workflows.
Questions or edge cases I missed? Reach out via LinkedIn or email — always happy to dig into a Drupal puzzle.