Automating Safety Documentation: A Free n8n Workflow for Construction Companies
I want you to do something for me. Go to your job trailer right now — or picture it if you are reading this after hours — and open the filing cabinet where you keep your safety documentation. Pull out the JHA folder. Count how many JHA forms are in there from this month. Now answer honestly: how many of them are actually specific to the work that was performed on that day, and how many are the same generic form with a different date written at the top?
If you are like most construction companies I work with across Volusia County — from general contractors in Daytona Beach to specialty trades in Port Orange, Ormond Beach, and DeLand — the answer is uncomfortable. Most of those JHAs are identical. The same hazards, the same controls, the same checkbox list that someone filled out in thirty seconds without looking up from their phone. They exist to satisfy a requirement, not to protect anyone.
And here is the thing that should keep you up at night: OSHA knows. OSHA inspectors have seen thousands of these generic forms. When they show up after an incident and find twenty identical JHAs in a row, they do not see a company that cares about safety. They see a company that is going through the motions. And going through the motions does not protect you from a $16,550 per-violation citation — or a $165,514 willful violation penalty if they decide you knew better.
Paper safety documentation has a 73 percent audit pass rate. Digital documentation has a 96 percent pass rate. That is not a marginal improvement. That is the difference between passing and failing an audit that could determine whether your company survives an incident.
This article gives you a complete, free system for automating your construction safety documentation. I am going to walk you through the entire pipeline: a mobile form your crews can fill out on their phones, a Python script that generates branded HTML documents from templates, an n8n workflow that converts those documents to PDFs and stores them in the cloud, and an automatic notification system that tells your safety manager the moment a document is submitted. By the end of this article, you will have everything you need to replace your filing cabinet with a digital safety documentation system that actually works.
Table of Contents
- Why Paper Safety Docs Are a Liability, Not a System
- What Safety Documentation Actually Needs to Cover
- The n8n Workflow Architecture: Mobile Form to Cloud PDF
- Building the Mobile Submission Form
- The Python HTML Template Engine for Safety PDFs
- PDF Generation and Cloud Storage
- Automatic Notification and Escalation
- Connecting to Your Existing Safety Stack
- The Complete n8n Workflow JSON
- Deployment and Crew Onboarding
- Frequently Asked Questions
Why Paper Safety Docs Are a Liability, Not a System
Let me be direct: if your safety documentation system involves paper forms in a filing cabinet, you do not have a safety documentation system. You have a liability.
Paper safety documents fail in every way that matters. They are hard to find when you need them. They cannot be searched. They get lost, damaged, or misfiled. They cannot prove when they were created — anyone can backdate a paper form, and OSHA knows that. Most critically, paper documents do not scale. When you are running three projects with fifteen field workers, someone has to physically create, collect, transport, organize, and file every single document. That is hours of administrative labor every week that produces no safety value — it just produces paper.
The construction industry is waking up to this reality. OSHA has been signaling for several years that digital documentation is not just acceptable but preferred. Digital inspection records with timestamps and photo evidence are the strongest defense in an OSHA investigation. A timestamped digital JHA with electronic crew sign-offs proves exactly when the document was created, who reviewed it, and that it was specific to the work performed. A paper form with "3/15/26" handwritten at the top proves nothing.
Here is the cost comparison that should make the decision obvious. A mid-size Florida construction company running three to five active projects spends an estimated five to eight hours per week on manual safety documentation — creating JHAs, printing toolbox talk materials, completing inspection checklists, filing everything, and occasionally trying to find a document that someone needs. At $85 per hour fully loaded, that is $425 to $680 per week, or $22,100 to $35,360 per year. The automated system I am about to show you reduces that to under one hour per week of actual human effort: reviewing generated documents and adding site-specific commentary. The rest is handled by software.
And the labor savings are actually the smaller benefit. The bigger benefit is the quality improvement. When safety documents are generated from validated templates with built-in hazard libraries, they are consistently better than what a tired superintendent produces by hand at the end of a long day. The template ensures every relevant hazard is listed, every required PPE item is included, and every control measure is documented. Human error in document creation drops to near zero because the humans are not creating documents anymore — they are reviewing them.
What Safety Documentation Actually Needs to Cover
Before we build the automation, let me clarify what a complete construction safety documentation system covers. Most companies think of JHAs and stop there. That is about a third of what you need.
Job Hazard Analyses (JHAs): Task-specific hazard identification and control documentation. A JHA should be completed before any new or changed work activity. It should list the specific hazards of that specific task at that specific site, not generic industry hazards. The crew should review it before starting work and sign off acknowledging the hazards and controls.
Toolbox Talks: Short safety briefings — typically ten minutes — on a focused topic. You should be conducting toolbox talks at least weekly on every project. Common topics include heat illness prevention (critical in Florida, where heat-related incidents spike between May and October), fall protection, electrical safety, trenching safety, and hazard communication. The documentation should include the topic, key points covered, date, presenter, and attendance sign-off.
Site Safety Inspections: Regular walkthroughs documenting the safety condition of the job site. Inspections should cover fall protection, housekeeping, electrical systems, fire prevention, PPE compliance, and any site-specific hazards. The documentation should include a checklist of items inspected, their status, any deficiencies found, and corrective actions required.
Incident Reports: Documentation of any workplace injury, near miss, or property damage. These need to be completed immediately after an incident, not at the end of the day or week. The documentation should include what happened, who was involved, what medical treatment was provided, what caused the incident, and what corrective actions were taken.
Training Records: Documentation that every worker has completed required safety training — OSHA 10/30, equipment-specific training, hazard communication, fall protection competent person training, and whatever else your projects require. Training records need to include the worker's name, the training topic, the date, the trainer, and evidence of completion.
The system I am building in this article handles the first three — JHAs, toolbox talks, and site safety inspections — because they are the highest-frequency documents and the ones most commonly mishandled. Incident reports and training records are important but less frequent and better served by dedicated platforms. Our guide on building a digital daily log for job sites covers the daily reporting workflow that complements this safety documentation system.
The n8n Workflow Architecture: Mobile Form to Cloud PDF
Here is the complete pipeline architecture. I want you to understand the full picture before we dive into the individual components.
Step 1: Mobile Form Submission. A superintendent or safety person fills out a structured form on their phone or tablet. The form collects the document type (JHA, toolbox talk, or inspection), the project name, date, task details, and crew information. The form can be built with JotForm, Google Forms, Typeform, or any form tool that can send a webhook POST request.
Step 2: Webhook Reception and Validation. The form submission hits an n8n webhook endpoint. The workflow validates that all required fields are present — doc_type, project_name, date, and submitted_by at minimum. If validation fails, it returns a clear error message so the field worker can correct and resubmit.
Step 3: Document Type Routing. A switch node routes the submission to the appropriate HTML template builder based on the doc_type field. JHAs go to the JHA builder, toolbox talks go to the toolbox builder, and inspections go to the inspection builder.
Step 4: HTML Template Generation. A code node generates a branded HTML document from the form data. The HTML includes your company branding, properly formatted hazard tables, PPE checklists, sign-off sections, and all the content from the form submission. The HTML uses inline CSS for consistent rendering when converted to PDF.
Step 5: PDF Conversion. The HTML is sent to Gotenberg — an open-source, self-hosted PDF conversion engine — which renders it to a professional PDF document. The result is a clean, branded safety document that looks like it came from an enterprise safety management platform.
Step 6: Cloud Storage. The PDF is uploaded to Google Drive (or your cloud storage of choice) in a structured folder hierarchy organized by project and document type.
Step 7: Logging and Notification. The document metadata is logged to a Google Sheets tracking sheet, and an email notification is sent to the safety manager with a link to the new document.
Step 8: Confirmation. The workflow responds to the original form submission with a success confirmation, so the field worker knows the document was received and processed.
The entire pipeline from form submission to PDF in the cloud takes about five seconds. Compare that to the forty-five minutes it takes to handwrite a JHA, drive it to the office, and file it in a cabinet.
Building the Mobile Submission Form
The form is the front door of the system. It needs to be fast, simple, and mobile-friendly. I recommend JotForm or Google Forms because both support webhook integrations and work well on phones.
Here are the required fields for each document type:
All document types: project_name (dropdown of active projects), date, submitted_by (auto-populated from login), doc_type (JHA / toolbox / inspection).
JHA additional fields: task_key (dropdown: concrete_pour, roofing, excavation, demolition, etc.), crew_lead, crew_size, site_specific_notes (free text for conditions unique to this site/day), weather_conditions.
Toolbox talk additional fields: topic_key (dropdown: heat_illness, fall_protection, electrical_safety, etc.), presenter, additional_notes, attendance_count.
Inspection additional fields: inspector, areas_inspected (multi-select), deficiencies_found (yes/no with conditional detail fields), overall_assessment (satisfactory / needs improvement / stop work).
The key design principle is progressive disclosure: show only the fields relevant to the selected document type. If someone selects "JHA," they see JHA fields. If they select "toolbox," they see toolbox fields. This keeps the form short and fast regardless of which document type is being created.
For offline capability — and you need offline capability on construction sites, especially in Florida where cell service can be spotty in rural Volusia County areas like west DeLand or parts of Deltona — JotForm supports offline mode where the form data is stored on the device and submitted automatically when connectivity returns. Google Forms has limited offline support but works well enough for most urban job sites in Daytona Beach, Port Orange, and Ormond Beach.
The Python HTML Template Engine for Safety PDFs
The Python script is the brains of the document generation. It takes structured data from the form submission and produces branded HTML ready for PDF conversion. Here is how to use it:
#!/usr/bin/env python3
"""
safety_doc_generator.py
Generates branded HTML safety documents from structured form data.
Outputs HTML ready for PDF conversion via n8n HTML-to-PDF pipeline.
Usage:
python safety_doc_generator.py --type jha --task concrete_pour --project "Daytona Beach Retail" --date 2026-03-20
python safety_doc_generator.py --type toolbox --topic heat_illness --project "Port Orange Condos"
python safety_doc_generator.py --type inspection --project "Ormond Beach Office" --date 2026-03-20
python safety_doc_generator.py --list-types
"""
import argparse
import html as html_mod
import json
import os
from datetime import datetime
from pathlib import Path
COMPANY_NAME = "Your Construction Co."
HAZARD_LIBRARY = {
"concrete_pour": {
"task": "Concrete Foundation Pour",
"ppe": ["Hard hat", "Safety glasses", "Steel-toe boots",
"High-vis vest", "Chemical-resistant gloves", "Hearing protection"],
"hazards": [
{"hazard": "Struck by concrete truck/pump boom", "severity": "HIGH",
"controls": "Spotter required; 15-ft exclusion zone from boom"},
{"hazard": "Silica dust inhalation", "severity": "MEDIUM",
"controls": "Wet pour method; N95 if cutting/grinding"},
{"hazard": "Slip/trip on wet concrete or rebar", "severity": "MEDIUM",
"controls": "Designated walkways; rubber boots in pour area"},
{"hazard": "Back strain from raking/finishing", "severity": "MEDIUM",
"controls": "Crew rotation every 30 min; mechanical screeding preferred"},
{"hazard": "Heat illness (Florida summer)", "severity": "HIGH",
"controls": "Water within 50 ft; shade break every 45 min; buddy system"},
{"hazard": "Chemical burn from wet concrete", "severity": "MEDIUM",
"controls": "Chemical-resistant gloves; wash station within 25 ft"},
],
},
"roofing": {
"task": "Roof Installation / Repair",
"ppe": ["Hard hat", "Safety glasses", "Steel-toe boots",
"Full-body harness + retractable lanyard", "High-vis vest", "Gloves"],
"hazards": [
{"hazard": "Fall from height (leading edge)", "severity": "CRITICAL",
"controls": "100% tie-off above 6 ft; guardrails or safety net"},
{"hazard": "Struck by falling tools/materials", "severity": "HIGH",
"controls": "Toe boards; tool lanyards; hard hats below"},
{"hazard": "Heat illness (roof temps 150F+)", "severity": "HIGH",
"controls": "6 AM start in summer; hydration every 20 min"},
{"hazard": "Nail gun puncture", "severity": "MEDIUM",
"controls": "Sequential trigger only; never disable safety"},
{"hazard": "Electrical contact (overhead lines)", "severity": "CRITICAL",
"controls": "Pre-work line survey; 10-ft minimum clearance"},
],
},
"excavation": {
"task": "Excavation and Trenching",
"ppe": ["Hard hat", "Safety glasses", "Steel-toe boots",
"High-vis vest", "Gloves", "Hearing protection"],
"hazards": [
{"hazard": "Trench collapse / cave-in", "severity": "CRITICAL",
"controls": "Slope/shore/box per OSHA Subpart P; competent person required"},
{"hazard": "Struck by excavator swing", "severity": "HIGH",
"controls": "Swing radius barricade; dedicated spotter"},
{"hazard": "Underground utility strike", "severity": "HIGH",
"controls": "Sunshine 811 locate 48 hr prior; hand-dig within 24 in"},
{"hazard": "Flooding / water accumulation", "severity": "MEDIUM",
"controls": "Dewatering pump staged; weather monitoring"},
{"hazard": "Hazardous atmosphere (deep trench)", "severity": "HIGH",
"controls": "Air monitoring if >4 ft; ventilation available"},
],
},
}
TOOLBOX_TOPICS = {
"heat_illness": {
"title": "Heat Illness Prevention on Florida Job Sites",
"points": [
"Symptoms: headache, nausea, confusion, hot dry skin — know them",
"Drink water every 15-20 minutes, do not wait until thirsty",
"Shade breaks every 45 minutes are mandatory, not optional",
"Buddy system: watch your partner for heat stress signs",
"New workers need acclimatization: 20% workload increase per day",
"OSHA can cite under General Duty Clause for heat failures",
],
},
"fall_protection": {
"title": "Fall Protection: Your Harness Is Not Optional",
"points": [
"100% tie-off at 6 feet or above — no exceptions",
"Inspect harness every use: webbing, stitching, D-rings, buckles",
"Anchor points must support 5,000 lbs per worker",
"Retractable lanyards: check retraction, fraying, clean housing",
"See someone unhooked? Stop them. It is everyone's job",
"Report damaged fall protection immediately",
],
},
"electrical_safety": {
"title": "Electrical Safety: Assume Every Wire Is Live",
"points": [
"10-foot clearance from overhead power lines (20 ft for cranes)",
"GFCI on all temporary power — test daily",
"Damaged cords: cut and discard, do not tape",
"Lockout/tagout before any electrical system work",
"Wet conditions + electricity = stop work immediately",
"Only qualified electricians on energized systems",
],
},
}
def generate_jha_html(task_key, project, date_str, crew_lead="", notes=""):
tmpl = HAZARD_LIBRARY.get(task_key)
if not tmpl:
return None
sev_colors = {"CRITICAL": "#dc2626", "HIGH": "#ea580c",
"MEDIUM": "#ca8a04", "LOW": "#16a34a"}
rows = ""
for h in tmpl["hazards"]:
c = sev_colors.get(h["severity"], "#6b7280")
rows += (f'<tr><td>{html_mod.escape(h["hazard"])}</td>'
f'<td style="color:{c};font-weight:bold;text-align:center">{h["severity"]}</td>'
f'<td>{html_mod.escape(h["controls"])}</td></tr>')
ppe = "".join(f"<li>{html_mod.escape(p)}</li>" for p in tmpl["ppe"])
signs = "".join(f'<tr><td>{i}.</td><td></td><td></td><td></td></tr>' for i in range(1, 11))
return f"""<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>JHA - {html_mod.escape(project)}</title>
<style>body{{font-family:Arial;margin:40px;color:#1a1a1a}}h1{{color:#1e40af;border-bottom:3px solid #1e40af}}
table{{width:100%;border-collapse:collapse;margin:12px 0}}th,td{{border:1px solid #d1d5db;padding:8px;font-size:13px}}
th{{background:#1e40af;color:white}}.ppe{{columns:2;list-style:none;padding:0}}.ppe li::before{{content:"\\2610 "}}</style>
</head><body><h1>JOB HAZARD ANALYSIS</h1>
<p><b>Project:</b> {html_mod.escape(project)} | <b>Task:</b> {html_mod.escape(tmpl["task"])} |
<b>Date:</b> {date_str} | <b>Crew Lead:</b> {html_mod.escape(crew_lead) or "________________"}</p>
{f"<p><b>Site Notes:</b> {html_mod.escape(notes)}</p>" if notes else ""}
<h2>Required PPE</h2><ul class="ppe">{ppe}</ul>
<h2>Hazard Analysis</h2><table><tr><th>Hazard</th><th>Severity</th><th>Controls</th></tr>{rows}</table>
<h2>Crew Sign-Off</h2><table><tr><th>#</th><th>Name</th><th>Signature</th><th>Date</th></tr>{signs}</table>
<p style="font-size:11px;color:#6b7280;margin-top:24px">Generated {datetime.now().isoformat()}</p>
</body></html>"""
def generate_toolbox_html(topic_key, project, date_str, presenter=""):
topic = TOOLBOX_TOPICS.get(topic_key)
if not topic:
return None
pts = "".join(f"<li>{html_mod.escape(p)}</li>" for p in topic["points"])
signs = "".join(f'<tr><td>{i}.</td><td></td><td></td></tr>' for i in range(1, 16))
return f"""<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>Toolbox Talk - {html_mod.escape(topic["title"])}</title>
<style>body{{font-family:Arial;margin:40px}}h1{{color:#15803d;border-bottom:3px solid #15803d}}
ol{{line-height:2}}table{{width:100%;border-collapse:collapse}}th,td{{border:1px solid #d1d5db;padding:6px}}
th{{background:#15803d;color:white}}</style></head>
<body><h1>TOOLBOX TALK: {html_mod.escape(topic["title"])}</h1>
<p><b>Project:</b> {html_mod.escape(project)} | <b>Date:</b> {date_str} |
<b>Presenter:</b> {html_mod.escape(presenter) or "________________"}</p>
<h2>Key Points</h2><ol>{pts}</ol>
<h2>Attendance</h2><table><tr><th>#</th><th>Name</th><th>Signature</th></tr>{signs}</table>
<p style="font-size:11px;color:#6b7280;margin-top:24px">Generated {datetime.now().isoformat()}</p>
</body></html>"""
def main():
ap = argparse.ArgumentParser(description="Safety Document HTML Generator")
ap.add_argument("--type", choices=["jha", "toolbox", "inspection"])
ap.add_argument("--task", help="JHA task key")
ap.add_argument("--topic", help="Toolbox topic key")
ap.add_argument("--project", default="Project Name")
ap.add_argument("--date", default=datetime.now().strftime("%Y-%m-%d"))
ap.add_argument("--crew-lead", default="")
ap.add_argument("--presenter", default="")
ap.add_argument("--notes", default="")
ap.add_argument("--output", help="Output file path")
ap.add_argument("--list-types", action="store_true")
args = ap.parse_args()
if args.list_types:
print("JHA tasks:", ", ".join(HAZARD_LIBRARY.keys()))
print("Toolbox topics:", ", ".join(TOOLBOX_TOPICS.keys()))
return
result = None
if args.type == "jha":
result = generate_jha_html(args.task, args.project, args.date, args.crew_lead, args.notes)
elif args.type == "toolbox":
result = generate_toolbox_html(args.topic, args.project, args.date, args.presenter)
if result and args.output:
Path(args.output).write_text(result, encoding="utf-8")
print(f"Saved: {args.output}")
elif result:
print(result)
else:
print("Error: check --type, --task, or --topic")
if __name__ == "__main__":
main()Run it like this:
# Generate a JHA for a concrete pour on a Daytona Beach project
python safety_doc_generator.py --type jha --task concrete_pour --project "Daytona Beach Retail Center" --date 2026-03-20 --crew-lead "Carlos Mendez" --output jha_concrete_2026-03-20.html
# Generate a heat illness toolbox talk
python safety_doc_generator.py --type toolbox --topic heat_illness --project "Port Orange Condos" --presenter "Mike Johnson" --output toolbox_heat_2026-03-20.html
# List all available templates
python safety_doc_generator.py --list-typesThe script generates clean HTML with inline CSS that renders consistently across PDF conversion engines. The hazard library is extensible — add your own task types by copying the pattern in the HAZARD_LIBRARY dictionary. Start with the templates provided, then add task types specific to your trade. An electrical contractor would add "panel_installation" and "conduit_run." A plumber would add "underground_piping" and "hot_water_system." Build the template once, generate documents forever.
The key advantage of template-based generation over manual creation is consistency. Every JHA generated from the "concrete_pour" template includes all six hazards specific to concrete work, with appropriate severity ratings and OSHA-aligned controls. A superintendent writing a JHA by hand might remember three of those six hazards on a good day. The template catches the ones humans forget — and the ones humans forget are usually the ones that cause injuries.
PDF Generation and Cloud Storage
The PDF conversion step uses Gotenberg, an open-source document conversion engine that runs as a Docker container. Gotenberg uses Chromium under the hood to render HTML to PDF, which means your documents look exactly the same in PDF as they do in a browser. No formatting surprises, no broken tables, no missing fonts.
To set up Gotenberg on your n8n server:
# Pull and run Gotenberg alongside n8n
docker run -d --name gotenberg -p 3000:3000 gotenberg/gotenberg:8
# Verify it is running
curl http://localhost:3000/healthIf you are using n8n Cloud instead of self-hosted n8n, you can use the PDFMunk community node or the CustomJS HTML-to-PDF node instead of Gotenberg. Both produce equivalent results — the difference is that Gotenberg is free and self-hosted, while PDFMunk charges per conversion.
For cloud storage, the workflow uploads the generated PDF to Google Drive in a structured folder hierarchy:
Safety Documents/
├── Daytona Beach Retail Center/
│ ├── JHAs/
│ │ ├── JHA_Concrete_Pour_2026-03-20.pdf
│ │ └── JHA_Roofing_2026-03-21.pdf
│ ├── Toolbox Talks/
│ │ └── Toolbox_Heat_Illness_2026-03-20.pdf
│ └── Inspections/
│ └── Inspection_2026-03-22.pdf
└── Port Orange Condos/
├── JHAs/
└── Toolbox Talks/This folder structure means that when an OSHA inspector asks for all safety documentation for a specific project, you can pull it up in seconds. No digging through filing cabinets. No calling the superintendent to ask where the forms are. Everything is organized, timestamped, and searchable.
You can substitute Google Drive with any cloud storage — Amazon S3, Microsoft OneDrive, Dropbox Business — by swapping the Google Drive node in the n8n workflow for the appropriate storage node. n8n supports all major cloud storage providers natively.
Automatic Notification and Escalation
The notification system is where this workflow goes from useful to indispensable. Getting documents generated and stored is great, but the real value is in the visibility and accountability the notifications create.
The basic notification sends an email to the safety manager whenever a new document is submitted. The email includes the document type, project name, who submitted it, and a direct link to the PDF in Google Drive. The safety manager can review the document from their phone without visiting the job site.
But the real power is in the escalation logic. Add a scheduled trigger to the workflow that runs at the end of each workday and checks which projects have submitted their required documents and which have not. If a project is supposed to have a daily JHA and no JHA was submitted by 3 PM, the workflow sends an escalation email to the superintendent and CC's the project manager. If it is still missing by 5 PM, a second escalation goes to the safety manager.
This accountability mechanism is what separates companies with real safety programs from companies with filing cabinets full of generic forms. When the superintendent knows that a missing JHA triggers automatic escalation, the JHA gets done. When they know that nobody checks, the JHA gets skipped.
For Slack-based teams, you can swap the email notification for a Slack message to a dedicated safety channel. I have worked with Daytona Beach contractors who run their entire safety communication through Slack channels organized by project. The automated document notifications feed right into that existing workflow — the safety manager sees new documents in the channel alongside the daily site photos, delivery confirmations, and crew updates.
Connecting to Your Existing Safety Stack
If you are already using a construction management platform like Procore, Fieldwire, or even just Google Sheets, the n8n workflow integrates with all of them.
Procore: n8n has a Procore integration that can push generated safety documents to the correct project's document repository. This means your safety docs appear alongside RFIs, submittals, and drawings in Procore's document management system, exactly where your team expects to find them.
Fieldwire: Use Fieldwire's API through n8n's HTTP request node to attach safety documents to specific tasks or locations within your Fieldwire project plan. The JHA for a concrete pour gets linked to the concrete pour task, so anyone looking at that task sees the safety documentation immediately.
Google Sheets: The logging step in the workflow already writes to Google Sheets. This gives you a live spreadsheet tracking every safety document generated across all projects — filterable by project, date, document type, and submitter. Build a pivot table on top of it and you have a safety documentation dashboard that updates in real time.
SafetyCulture / iAuditor: If you are using SafetyCulture for safety audits, the n8n workflow can push inspection results to SafetyCulture's API, giving your safety manager a single platform to review both automated inspections and manual audit results.
The principle is the same in every case: the n8n workflow is the engine that generates and processes documents, and it pushes the results to wherever your team already works. You do not have to change how people access information — you just change how it gets there.
The Complete n8n Workflow JSON
Here is the full n8n workflow you can import directly into your n8n instance. Copy this JSON, open n8n, click "Import from JSON" in the workflow editor, and paste it in.
{
"name": "Safety Documentation Pipeline",
"nodes": [
{
"name": "Webhook — Safety Form",
"type": "n8n-nodes-base.webhook",
"position": [200, 300],
"parameters": {
"httpMethod": "POST",
"path": "safety-doc",
"authentication": "headerAuth",
"responseMode": "responseNode"
}
},
{
"name": "Validate Fields",
"type": "n8n-nodes-base.if",
"position": [400, 300],
"parameters": {
"conditions": {
"boolean": [
{ "value1": "={{ $json.doc_type !== '' }}", "value2": true },
{ "value1": "={{ $json.project_name !== '' }}", "value2": true },
{ "value1": "={{ $json.date !== '' }}", "value2": true },
{ "value1": "={{ $json.submitted_by !== '' }}", "value2": true }
]
}
}
},
{
"name": "Route by Type",
"type": "n8n-nodes-base.switch",
"position": [600, 250],
"parameters": {
"dataPropertyName": "doc_type",
"rules": {
"rules": [
{ "value": "jha" },
{ "value": "toolbox" },
{ "value": "inspection" }
]
}
}
},
{
"name": "Build JHA HTML",
"type": "n8n-nodes-base.code",
"position": [800, 150],
"parameters": {
"jsCode": "const d=$input.first().json;const rows=(d.hazards||[]).map(h=>`<tr><td>${h.hazard}</td><td>${h.severity}</td><td>${h.controls}</td></tr>`).join('');const html=`<html><head><style>body{font-family:Arial;margin:40px}h1{color:#1e40af}table{width:100%;border-collapse:collapse}th,td{border:1px solid #ccc;padding:8px}th{background:#1e40af;color:#fff}</style></head><body><h1>JOB HAZARD ANALYSIS</h1><p><b>Project:</b> ${d.project_name} | <b>Task:</b> ${d.task_name} | <b>Date:</b> ${d.date}</p><table><tr><th>Hazard</th><th>Severity</th><th>Controls</th></tr>${rows}</table></body></html>`;return[{json:{...d,html_content:html,filename:`JHA_${d.project_name.replace(/\\s/g,'_')}_${d.date}.pdf`}}];"
}
},
{
"name": "Build Toolbox HTML",
"type": "n8n-nodes-base.code",
"position": [800, 300],
"parameters": {
"jsCode": "const d=$input.first().json;const pts=(d.key_points||[]).map(p=>`<li>${p}</li>`).join('');const html=`<html><head><style>body{font-family:Arial;margin:40px}h1{color:#15803d}ol{line-height:2}</style></head><body><h1>TOOLBOX TALK: ${d.topic_title}</h1><p><b>Project:</b> ${d.project_name} | <b>Date:</b> ${d.date}</p><ol>${pts}</ol></body></html>`;return[{json:{...d,html_content:html,filename:`Toolbox_${d.topic_title.replace(/\\s/g,'_')}_${d.date}.pdf`}}];"
}
},
{
"name": "Build Inspection HTML",
"type": "n8n-nodes-base.code",
"position": [800, 450],
"parameters": {
"jsCode": "const d=$input.first().json;const items=(d.checklist_items||[]).map(i=>`<tr><td>${i.ok?'Yes':'No'}</td><td>${i.description}</td><td>${i.notes||''}</td></tr>`).join('');const html=`<html><head><style>body{font-family:Arial;margin:40px}h1{color:#7c3aed}table{width:100%;border-collapse:collapse}th,td{border:1px solid #ccc;padding:6px}th{background:#7c3aed;color:#fff}</style></head><body><h1>SITE SAFETY INSPECTION</h1><p><b>Project:</b> ${d.project_name} | <b>Date:</b> ${d.date}</p><table><tr><th>OK?</th><th>Item</th><th>Notes</th></tr>${items}</table></body></html>`;return[{json:{...d,html_content:html,filename:`Inspection_${d.project_name.replace(/\\s/g,'_')}_${d.date}.pdf`}}];"
}
},
{
"name": "HTML to PDF (Gotenberg)",
"type": "n8n-nodes-base.httpRequest",
"position": [1050, 300],
"parameters": {
"method": "POST",
"url": "http://gotenberg:3000/forms/chromium/convert/html",
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{ "name": "files", "value": "={{ $json.html_content }}" }
]
},
"options": { "response": { "response": { "responseFormat": "file" } } }
}
},
{
"name": "Upload to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [1250, 300],
"parameters": {
"operation": "upload",
"name": "={{ $json.filename }}",
"folderId": "YOUR_SAFETY_DOCS_FOLDER_ID"
}
},
{
"name": "Log to Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [1450, 250],
"parameters": {
"operation": "append",
"sheetId": "YOUR_SHEET_ID",
"range": "SafetyDocs!A:G",
"columns": "date,project_name,doc_type,submitted_by,filename,drive_url,created_at"
}
},
{
"name": "Notify Safety Manager",
"type": "n8n-nodes-base.emailSend",
"position": [1450, 400],
"parameters": {
"toEmail": "safety@company.com",
"subject": "Safety Doc: {{ $json.doc_type }} — {{ $json.project_name }}",
"text": "{{ $json.submitted_by }} submitted a {{ $json.doc_type }} for {{ $json.project_name }}.\nDocument: {{ $json.filename }}\nLink: {{ $json.drive_url }}"
}
},
{
"name": "Respond OK",
"type": "n8n-nodes-base.respondToWebhook",
"position": [1650, 300],
"parameters": {
"respondWith": "json",
"responseBody": "{ \"status\": \"success\", \"message\": \"Document generated and stored\" }"
}
},
{
"name": "Respond Error",
"type": "n8n-nodes-base.respondToWebhook",
"position": [600, 450],
"parameters": {
"respondWith": "json",
"responseBody": "{ \"status\": \"error\", \"message\": \"Missing required fields\" }",
"options": { "responseCode": 400 }
}
}
],
"connections": {
"Webhook — Safety Form": { "main": [[{ "node": "Validate Fields" }]] },
"Validate Fields": {
"main": [[{ "node": "Route by Type" }], [{ "node": "Respond Error" }]]
},
"Route by Type": {
"main": [
[{ "node": "Build JHA HTML" }],
[{ "node": "Build Toolbox HTML" }],
[{ "node": "Build Inspection HTML" }]
]
},
"Build JHA HTML": { "main": [[{ "node": "HTML to PDF (Gotenberg)" }]] },
"Build Toolbox HTML": { "main": [[{ "node": "HTML to PDF (Gotenberg)" }]] },
"Build Inspection HTML": {
"main": [[{ "node": "HTML to PDF (Gotenberg)" }]]
},
"HTML to PDF (Gotenberg)": {
"main": [[{ "node": "Upload to Google Drive" }]]
},
"Upload to Google Drive": {
"main": [
[{ "node": "Log to Sheets" }, { "node": "Notify Safety Manager" }]
]
},
"Log to Sheets": { "main": [[{ "node": "Respond OK" }]] },
"Notify Safety Manager": { "main": [[{ "node": "Respond OK" }]] }
}
}After importing, you need to configure three things: your Google Drive credentials and folder ID, your Google Sheets credentials and sheet ID, and your email sending credentials. The Gotenberg URL defaults to http://gotenberg:3000 which works if you are running both n8n and Gotenberg as Docker containers on the same network. If they are on different hosts, update the URL accordingly.
Deployment and Crew Onboarding
Getting the technology running is the easy part. Getting your crews to use it is the hard part. Here is the deployment sequence I recommend, based on what has worked for construction companies across the Daytona Beach metro area.
Week 1: Internal testing. Run the workflow yourself. Submit test forms for each document type. Verify the PDFs look right, the cloud storage is organized correctly, and the notifications are going to the right people. Fix any issues before involving field crews.
Week 2: Safety manager pilot. Have your safety manager use the system exclusively for one week. They are your first advocate — if they love it, adoption by the field crews becomes dramatically easier. Have them submit JHAs, toolbox talks, and inspection reports through the mobile form and verify that the output meets their quality standards.
Week 3: One crew pilot. Pick your most tech-friendly superintendent and their crew. Run the digital system on one project while everything else stays on paper. The goal is to prove that the digital system is faster and easier than paper, not to force adoption. Let the pilot crew discover the benefits themselves.
Week 4: Expand. Once the pilot crew is comfortable, expand to all crews. Have the pilot superintendent demonstrate the system to the other superintendents. Peer-to-peer adoption works better than top-down mandates in construction, as I mentioned in our article on five manual processes costing contractors money. Nobody wants to be told by the office what to do, but everyone wants to do less paperwork.
Ongoing: Template expansion. As crews use the system, they will identify task types that need new templates. Build new JHA templates as needed — the Python script's hazard library is designed to grow. Over three to six months, you will build a comprehensive template library that covers every significant activity your company performs.
The most important thing about crew onboarding is this: the digital form must be faster and easier than the paper form it replaces. If it takes longer to fill out the digital form than it takes to fill out a paper form, adoption will fail. Keep the forms short. Use dropdowns instead of free text wherever possible. Pre-populate fields that do not change (company name, project name, superintendent name). Make the form completable in under five minutes on a phone, and your crews will use it because it saves them time.
For construction companies in the Daytona Beach area and across Volusia County, our automation team implements these safety documentation systems with hands-on support — from n8n setup and form design to crew training and template development. We have deployed this exact workflow for general contractors, roofing companies, concrete subcontractors, and excavation firms throughout Central Florida.
Frequently Asked Questions
Is digital safety documentation accepted by OSHA? Yes. OSHA requires safety documentation to be accurate, complete, and accessible — there is no requirement for paper. Digital documentation with timestamps, electronic signatures, and structured data actually exceeds OSHA's expectations for documentation quality. Digital records have a 96 percent audit pass rate compared to 73 percent for paper records. The key requirement is that documents must be producible on demand during an inspection.
How much does the n8n safety documentation workflow cost to run? The software is free. n8n is open-source and can be self-hosted at no cost. Gotenberg is open-source. Google Drive and Google Sheets have free tiers. The only cost is the server to run n8n and Gotenberg — a basic virtual private server at $10 to $20 per month handles it comfortably. If you prefer managed hosting, n8n Cloud starts at $20 per month with the PDF conversion handled by a community node.
Can my field crews use the forms without cell service? Yes, with the right form tool. JotForm supports offline mode where form submissions are cached on the device and sent automatically when connectivity returns. This is critical for job sites in rural areas of Volusia County — west of Interstate 4, cell coverage can be unreliable. The offline capability means your superintendent fills out the form whenever they are ready, and the workflow processes it when the phone reconnects.
How long does it take to set up the complete system? A technically comfortable person can have the basic system running in a day: install n8n and Gotenberg via Docker, import the workflow JSON, create the mobile form, and test the pipeline. Building out the full template library and customizing the HTML templates for your company branding takes another two to three days. Crew onboarding and adoption takes two to four weeks using the phased approach described in this article.
Can I add custom JHA templates for my specific trade? Absolutely. The Python script's hazard library is a dictionary you can extend with any task type. Copy an existing template, change the task name, update the hazards and controls, and the new task type is immediately available. Electricians, plumbers, HVAC contractors, and concrete specialists have all added trade-specific templates to the library. The format is intentionally simple so that anyone who can edit a text file can add a new template.
Every safety document that exists only on paper is one OSHA inspection away from being a liability instead of a defense. The workflow in this article replaces your filing cabinet with a system that generates professional documents in seconds, stores them securely in the cloud, and proves to any inspector that your company takes safety seriously — not because you have a stack of paper, but because you have timestamped, searchable, verifiable documentation that was created before the work started, not after the incident happened.
For construction companies ready to automate their safety documentation, our automation team deploys this exact n8n workflow with custom templates for your trade, branded PDF output, and hands-on crew training. The system pays for itself the first time an OSHA inspector finds exactly the document they are looking for in under ten seconds.