Consent and GDPR for a small website: how to comply without buying a plugin
Two years ago, an administrative firm in Salamanca received a letter from the Spanish DPA (AEPD). A fine of 3,000 euros. Not from a breach or data leak. They had grabbed a free contact form from the internet, pasted it into their website, and had no cookie notice. They weren't collecting sensitive data. But they were collecting data. Without consent. That's the violation.
The pattern repeats. A small website, an owner who knows HTML but not law, and suddenly they need to "comply with GDPR" with no idea where to start. And of course, the first thing you see on Google is an ad: "All-in-one GDPR solution, 29 euros per month".
You don't need that. A privacy policy, a cookie banner, a form that requests consent, and clean HTML code you can write in an afternoon. This is what you actually need. No plugin, no subscription, no legal jargon nobody understands.
What the law actually says
GDPR is the General Data Protection Regulation. It came into force in May 2018 across the EU. The AEPD, the Spanish Data Protection Authority, is who oversees it here.
The key point: if your website collects personal data from people (names, emails, browsing behaviour), you have to tell them what you do with it, and you must ask permission.
This isn't a suggestion. It's mandatory.
Fines vary. The AEPD can sanction from 100 euros up to 20 million or 4% of annual turnover, depending on the case. For an SME with 100,000 euros turnover, 4% is 4,000 euros. For one with a million, it's 40,000.
Why the fines? Not always malice. The Salamanca firm wasn't criminal. They simply copied code without checking what happened to the data.
There are four obligations:
- Notify that you use cookies or trackers.
- Request consent before installing non-essential cookies.
- Have a privacy policy explaining what you do with data.
- Let people delete their data if they ask.
That's it. You don't need a negotiated DPA, you don't need external audit, you don't need a 15,000 euro consultant. You need clarity. And that costs nothing.
Cookies: what they are and which ones need consent
A cookie is a tiny file the browser stores. It has a name, a value, an expiration date. Nothing else.
Examples:
- Session cookie: remember that you're logged in.
- Analytics cookie: count how many people visit.
- Advertising cookie: show ads based on what you viewed before.
The AEPD distinguishes between two types.
Essential cookies: required for the website to function. Session, shopping cart, language preferences. These don't need consent.
Non-essential cookies: Google Analytics, Meta Pixel, Intercom, any third-party script. These need prior consent. The user must accept them before they load.
How do you know if a cookie is essential or not? Ask: "If I disable this cookie, does the website stop working?" If yes, it's essential. If no, it needs consent.
Google Analytics isn't essential. The website works without it. So it needs consent.
A contact form is essential, because without it you can't receive messages. So it doesn't need cookie consent. But it does need consent for processing personal data (name, email, phone).
Minimum structure: notice, policy, form
Any small website needs three things:
- A banner or notice saying "we use cookies".
- A privacy policy page explaining what you do.
- Forms that request explicit consent.
It's not complicated. Let's go through each one.
1. Cookie banner that complies
The banner must be visible when the user arrives. It must have two buttons of equal size: "Accept" and "Reject". You can't hide the reject button or make the accept button larger. The AEPD watches for this.
The banner text must be clear. Something like:
"This website uses analytics cookies (Google Analytics) to count visitors and improve the experience. We also use session cookies so the contact form works. Accepting means you allow small information to be stored in your browser. You can reject or change preferences at any time."
Then two buttons side by side, same size, same colour (or different colours but visually equal).
Here's the HTML and JavaScript you need:
<!-- Cookie banner -->
<div id="cookie-banner" class="cookie-banner">
<div class="cookie-content">
<h2>Cookies and privacy</h2>
<p>This website uses analytics cookies (Google Analytics) to understand how you use it. It also uses session cookies so the form works. You can accept, reject, or change preferences at any time.</p>
<div class="cookie-buttons">
<button id="cookie-reject" class="btn-secondary">Reject</button>
<button id="cookie-accept" class="btn-primary">Accept</button>
</div>
<a href="/privacy-policy" class="cookie-link">View privacy policy</a>
</div>
</div>
<style>
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #f9f9f9;
border-top: 1px solid #ddd;
padding: 20px;
font-size: 14px;
z-index: 9999;
box-shadow: 0 -2px 8px rgba(0,0,0,0.1);
}
.cookie-content {
max-width: 600px;
margin: 0 auto;
}
.cookie-content h2 {
margin: 0 0 10px 0;
font-size: 16px;
}
.cookie-content p {
margin: 0 0 15px 0;
line-height: 1.5;
color: #333;
}
.cookie-buttons {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.btn-primary, .btn-secondary {
padding: 10px 20px;
border: 1px solid #333;
background: white;
color: #333;
cursor: pointer;
font-size: 14px;
flex: 1;
}
.btn-primary {
background: #333;
color: white;
}
.btn-secondary {
background: white;
color: #333;
}
.btn-primary:hover {
background: #555;
}
.btn-secondary:hover {
background: #f0f0f0;
}
.cookie-link {
display: inline-block;
font-size: 12px;
color: #666;
text-decoration: underline;
}
.hidden {
display: none !important;
}
</style>
<script>
// Cookie consent management
(function() {
const banner = document.getElementById('cookie-banner');
const acceptBtn = document.getElementById('cookie-accept');
const rejectBtn = document.getElementById('cookie-reject');
// Check if the user already decided
function checkConsentStatus() {
const consent = localStorage.getItem('cookie-consent');
if (consent) {
banner.classList.add('hidden');
if (consent === 'accepted') {
loadAnalytics();
}
}
}
// Accept cookies
acceptBtn.addEventListener('click', function() {
localStorage.setItem('cookie-consent', 'accepted');
localStorage.setItem('cookie-date', new Date().toISOString());
banner.classList.add('hidden');
loadAnalytics();
// Reload page so Google Analytics initialises
location.reload();
});
// Reject cookies
rejectBtn.addEventListener('click', function() {
localStorage.setItem('cookie-consent', 'rejected');
localStorage.setItem('cookie-date', new Date().toISOString());
banner.classList.add('hidden');
});
// Load Google Analytics only if user accepted
function loadAnalytics() {
const script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXX';
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-XXXXX');
}
// Execute on page load
checkConsentStatus();
})();
</script>
Note: replace G-XXXXX with your Google Analytics ID.
The flow is straightforward:
- Page loads. Banner appears.
- User clicks "Accept" or "Reject".
- The choice is stored in localStorage (the user's browser).
- If they accepted, Google Analytics loads. If they rejected, it doesn't.
- Next time they visit, the banner doesn't appear.
That's all. No third-party plugin. No external services. No subscription.
2. Privacy policy: minimum template
This is where many SMEs fall short. Either they post a 20-page PDF that reads like a banking merger contract, or nothing at all.
The policy needs these sections:
- Who is responsible (you, your company).
- What data you collect (name, email, IP).
- Why you collect it (process forms, improve website).
- Who you share it with (Google Analytics, nobody else).
- How long you keep it.
- User rights (access, correction, deletion).
- How to contact if there's a problem.
Here's the complete template, tailored for a small website:
# Privacy Policy
## 1. Responsible party
[Your name] ([Your company]), based at [Your address] with tax ID [Your tax ID/NIF], is responsible for processing your personal data.
Contact: [Your email] | Phone: [Your phone]
## 2. Data we collect
We collect data you voluntarily give us:
- Name
- Email address
- Phone (optional)
- Message or enquiry
- Subject
We do not collect automatic data except:
- IP address: to manage the server and detect abuse.
- Referrer: to see where you came from (Google, social media).
- User-Agent: to know what browser you use (Chrome, Firefox).
## 3. Why we collect data
We use the data you send us to:
- Answer your enquiry.
- Contact you if we need to clarify something.
- Improve the website based on how you use it.
- Meet legal obligations (invoices, tax records).
We don't profile or segment. We don't sell data. We don't share it with third parties for marketing.
## 4. Cookies and trackers
### Essential cookies (do not need consent)
- Session: so the form works.
- Language preference: to remember which language you chose.
### Non-essential cookies (need consent)
- Google Analytics (ID: G-XXXXX): count visitors, see which pages are popular.
- Cloudflare: protect the website from DDoS attacks.
If you reject non-essential cookies, the website works normally, just without analytics.
## 5. Who we share data with
- Google Analytics: Google processes visit data.
- Cloudflare: Cloudflare processes security data.
- Nobody else.
Both have GDPR agreements. Google and Cloudflare don't use your data for advertising.
## 6. How long we keep data
- Contact form data: 12 months. After that, deleted.
- Session cookies: expire when you close the browser.
- Analytics cookies: Google Analytics retains for 14 months.
If you request deletion, we delete within 30 days.
## 7. Your rights
You have the right to:
- Access: know what data we have about you.
- Correction: fix incorrect data.
- Deletion: ask us to remove it (right to be forgotten).
- Restriction: ask us not to process some data.
- Portability: receive your data in downloadable format.
- Objection: reject any data processing.
To exercise these rights, email [your email] with subject "GDPR Rights" and your request. We respond within 30 days.
## 8. Security
We use HTTPS (connection is encrypted). We don't store passwords. Form data is stored in [where: Google Drive, private database, email]. No public access.
## 9. Changes to this policy
If we change something important, we'll announce it on the website. This policy is updated regularly. Last updated: [today's date].
## 10. Contact the Spanish DPA
If you have a complaint about how we handle your data, you can contact the Spanish DPA (AEPD):
- Website: www.aepd.es
- Phone: 900 100 100
- Address: Paseo de la Castellana 141, Madrid
That's it. One page. Downloadable. Understandable without a lawyer. It complies.
3. Contact form that requests consent
This is where data processing starts. A contact form receives name, email, phone. That's personal data. You need explicit permission.
Many people get confused: "If someone sends me a form, do I need them to consent?" Yes. Submitting the form isn't automatic consent. You need a checkbox saying "I agree you can process my information according to the privacy policy".
Here's the code:
<form id="contact-form" class="contact-form">
<div class="form-group">
<label for="name">Name *</label>
<input type="text" id="name" name="name" required aria-label="Your name">
</div>
<div class="form-group">
<label for="email">Email address *</label>
<input type="email" id="email" name="email" required aria-label="Your email">
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" aria-label="Your phone">
</div>
<div class="form-group">
<label for="message">Message *</label>
<textarea id="message" name="message" required rows="5" aria-label="Your message"></textarea>
</div>
<div class="form-group checkbox">
<input type="checkbox" id="consent" name="consent" required>
<label for="consent">
I agree you can process my name, email and phone according to the
<a href="/privacy-policy" target="_blank">privacy policy</a>. *
</label>
</div>
<button type="submit" class="btn-submit">Send</button>
<div id="form-message" class="form-message"></div>
</form>
<style>
.contact-form {
max-width: 500px;
margin: 30px auto;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: inherit;
font-size: 14px;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #333;
box-shadow: 0 0 4px rgba(0,0,0,0.1);
}
.form-group.checkbox {
display: flex;
align-items: flex-start;
gap: 10px;
}
.form-group.checkbox input {
width: auto;
margin-top: 3px;
}
.form-group.checkbox label {
margin: 0;
font-size: 14px;
}
.form-group.checkbox a {
color: #333;
}
.btn-submit {
padding: 12px 30px;
background: #333;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-submit:hover {
background: #555;
}
.btn-submit:disabled {
background: #ccc;
cursor: not-allowed;
}
.form-message {
margin-top: 15px;
padding: 10px;
border-radius: 4px;
display: none;
}
.form-message.success {
background: #d4edda;
color: #155724;
display: block;
}
.form-message.error {
background: #f8d7da;
color: #721c24;
display: block;
}
</style>
<script>
document.getElementById('contact-form').addEventListener('submit', async function(e) {
e.preventDefault();
const form = this;
const messageDiv = document.getElementById('form-message');
const submitBtn = form.querySelector('.btn-submit');
// Basic validation
if (!form.consent.checked) {
messageDiv.textContent = 'You must accept the privacy policy.';
messageDiv.classList.remove('success');
messageDiv.classList.add('error');
return;
}
// Prepare the data
const formData = new FormData(form);
const data = {
name: formData.get('name'),
email: formData.get('email'),
phone: formData.get('phone') || 'Not provided',
message: formData.get('message'),
consent: true,
timestamp: new Date().toISOString()
};
// Send to your server
submitBtn.disabled = true;
submitBtn.textContent = 'Sending...';
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
messageDiv.textContent = 'Thanks. We will be in touch soon.';
messageDiv.classList.remove('error');
messageDiv.classList.add('success');
form.reset();
submitBtn.disabled = false;
submitBtn.textContent = 'Send';
} else {
throw new Error('Server error');
}
} catch (error) {
messageDiv.textContent = 'Error sending. Please try again.';
messageDiv.classList.remove('success');
messageDiv.classList.add('error');
submitBtn.disabled = false;
submitBtn.textContent = 'Send';
}
});
</script>
Key points:
- The checkbox is marked
required. The form can't be submitted without accepting. - The text explicitly states which data is processed (name, email, phone).
- There's a link to the privacy policy.
- On submit, timestamp and consent are recorded for audit purposes.
On your backend, when you receive the data, also save:
- User's IP
- Exact date and time
- That consent was given
This way, if the AEPD audits you, you can prove: "The user sent name, email, phone, AND checked they accepted the policy. Everything documented."
Managing rights: access, deletion, correction
People have the right to ask you to delete their data. Or to show them what you have stored.
It's not complicated if you plan from the start. In your form, besides name/email/message, save:
- Unique ID (a number that doesn't repeat).
- Timestamp (exact date and time).
- User's IP (for audit, optional).
- Consent status (accepted/rejected).
When someone emails saying "Please delete my data", you have 30 days to:
- Find all records with their email.
- Delete them.
- Confirm it's done.
A simple script in your database (if you use one):
-- Example with SQLite or similar
DELETE FROM contact_form
WHERE email = 'user@example.com'
AND timestamp < date('now', '+30 days');
If you just use Google Forms or a service that stores data, you have to go manually delete. That's why it's better to have control: download the data regularly, delete it when the time period is up.
What makes a small website compliant
Summarised:
- Visible banner: appears on arrival, two equal buttons, explains which cookies you use.
- Privacy policy: one page with what data you collect, why, who you share it with, how long you keep it.
- Explicit consent in forms: mandatory checkbox stating which data is being processed.
- Documentation: keep copies of consent, timestamps, IPs. If you're audited, you prove you asked permission.
- Right to deletion: you have a process to delete data if someone asks.
- Automatic deletion: after 12 months (or whatever period you use), delete old data.
That's enough for a small website. If you grow later, you add more rigour. But this is the bare minimum.
Mistakes you see all the time
Mistake 1: ignore third-party cookies
You installed a chat widget (Intercom, Drift), a discount popup (Privy), Google Analytics. All load cookies. Your banner isn't enough. Each third-party service also needs to respect consent.
The answer is to check that each service has its own privacy policy and respects the GDPR standard. Most do. Google Analytics, for example, won't load if you reject consent (if you configure it properly).
Mistake 2: store data forever
"Well, the data is here, just in case." Wrong. The law says "limited time". For a contact form, 12 months is reasonable. After that, delete.
Mistake 3: make rejection hard
The reject button is hidden, tiny, or requires clicking through 5 things. The AEPD watches for this. Both buttons must be equally easy to access.
Mistake 4: pre-tick cookies
Some old banners came with checkboxes already ticked ("If you do nothing, you accept"). That doesn't work. It has to be opt-in: the user checks the box themselves.
Mistake 5: don't update the policy
Your policy says "We don't use Google Analytics" but now you do. That's non-compliance. Every time you change, update the policy and announce it.
Free tools that help
You don't need a plugin, but some free services can help:
- Termly: auto-generates privacy policy. You answer questions, it generates the text. Free for small websites.
- Privacy Policy Generator: similar to Termly.
- Google Analytics (GDPR-compliant version): if you set it up right, Google Analytics respects consent.
None of these are essential. All the tools here are open source or can be replicated with basic HTML/JavaScript.
Implementation checklist (step by step)
Week 1:
- Write privacy policy (use the template, personalise with your data).
- Publish on a visible page (e.g.
/privacy-policy). - Update terms of service if you have one (declare you use cookies).
Week 2:
- Install cookie banner (copy the HTML/JS, adjust styles).
- Verify Google Analytics only loads if user accepted.
- Test: reject cookies, reload, check Google Analytics didn't load.
- Test: accept cookies, check it loads.
Week 3:
- Review forms (add consent checkbox to all).
- Verify they save timestamp and consent (audit trail).
- Create process for deleting old data (automatic script or manual every 30 days).
Week 4:
- Back up policy and code (in case of audit).
- Document where you store data (Google Drive, private database, email).
- Set up alerts: if someone asks to "delete my data", you know where to act.
This is realistic for a small website. It's not complex. It's organisation.
Real costs
- Hosting: 5-20 euros per month (whether you do GDPR or not).
- Domain: 10 euros per year.
- Cookie plugin: 0 euros (you use your own JavaScript).
- Legal consultant: 500-2,000 euros if you hire. Optional.
- Liability insurance: 200-500 euros per year if you hold sensitive data.
If you do this yourself: 150-240 euros per year. If you hire a consultant to review: +500 euros, one-time.
Much less than 29 euros per month forever.
Links and references
All verified:
- Official AEPD cookie guide
- AEPD: contact and resources
- EDPB Guidelines on Cookie Walls
- Article 4.11 GDPR: consent definition
- GDPR rights (access, deletion, portability)
Closing thought
GDPR compliance isn't buying a 29 euro per month plugin. It's telling people what you do with their data, asking permission, and respecting it.
For a small website: a clear policy, an honest banner, a form that requests consent. That's 80% of the work.
The remaining 20% is discipline: update when you change services, delete old data, respond if someone asks for access or deletion.
The Salamanca firm could have avoided the 3,000 euros. They just needed a banner, a policy, and a checkbox. Three hours of work.
There it is. No plugin. No subscription. No legal smoke.

