GDPR Landing Pages with Disk and Azure Export

~25 min Advanced

Build a GDPR-compliant landing page in BptLandingPage's visual editor, then persist the project file to local disk or Azure Blob Storage and reload it for future edits.

Home / Learning / GDPR Landing Pages with Disk and Azure Export
The whole picture BptLandingPage is a visual editor that produces a .bptl project file. Inside the editor you also configure a GDPR consent popup that ships with the page. To persist the project across browser sessions — for staff editing or for multi-tenant landing pages — you route the editor's OnSave bytes to whatever storage you prefer: local disk, Azure Blob Storage, or anything else.

Part 1: GDPR support

Step 1: Enable the GDPR popup

Inside the editor, open the Project settings panel and tick GDPR popup. The editor exposes the full GdprPopupSettings object through a structured form:

SettingOptionsWhat it controls
RenderLocation BottomBanner, TopBanner, CenterModal, BottomLeftCorner, BottomRightCorner Where the popup appears on the visitor's first view.
ConsentCategories Necessary (always on, locked), Functional, Analytics, Marketing Per-category toggles the visitor sees. Necessary cannot be opted out of.
FrameworkBadge DataPrivacyFramework, IsoIec27701, IabTcf, IcoUk Optional compliance badge rendered next to the popup — signals which framework you follow.
ExternalProvider None, OneTrust, CookieBot, CookieInformation, Ecomply, TrustArc, HubSpot If non-None, the popup defers to an externally-hosted consent script instead of rendering its own UI.
ConsentEventName String (default bpt-consent-changed) The CustomEvent name dispatched on window whenever consent state changes.

Step 2: Listen for consent on the customer page

The exported page exposes a global window.bptConsent API. Use it from your own analytics or marketing scripts to gate behavior on consent:

// Read the current consent state on page load. const consent = window.bptConsent.getConsent(); if (consent?.categories?.analytics) { // Fire your analytics pixel. } // React to changes during the visit. window.addEventListener("bpt-consent-changed", e => { if (e.detail.categories.marketing) { loadRemarketingTags(); } }); // Programmatically update consent (e.g. from a "Cookie settings" link in your footer). window.bptConsent.setConsent({ marketing: true, analytics: true }); // Re-open the popup so the visitor can change their mind. window.bptConsent.show();

Step 3: Export the GDPR module as standalone JS

Once the popup is configured, the editor's "Export GDPR Module (.js)" button produces a self-contained JavaScript file. Host it on your CDN and reference it from any page that needs the same consent UI — the same configuration can be reused across an unlimited number of landing pages.

<!-- on any page that should show the same popup --> <script src="https://cdn.example.com/consent/v1.js" defer></script>

Part 2: Persistence — disk and Azure

Step 4: Capture the editor's save bytes

The editor saves to the native .bptl format, which is a self-contained project envelope. Wire SaveMode="Both" so the bytes flow to your callback in addition to (or instead of) the browser download:

@page "/admin/landing/{Slug}" @using Bpt.Components.Tools @inject ILandingPageStore Store <BptLandingPage @bind-Value="_project" Height="85vh" Mode="LandingPageMode.Edit" EnableSave="true" EnableExport="true" SaveMode="LandingPageSaveMode.Both" OnSave="HandleSave" /> @code { [Parameter] public string Slug { get; set; } = ""; private LandingPageProject? _project; protected override async Task OnInitializedAsync() { var bytes = await Store.LoadAsync(Slug); if (bytes is { Length: > 0 }) _project = BptlSerializer.Deserialize(bytes); } private async Task HandleSave(byte[] bytes) { await Store.SaveAsync(Slug, bytes); } }

Step 5a: Persist to local disk

The simplest store is the server's filesystem. Drop the bytes in wwwroot/landingpages/ and you're done:

public sealed class DiskLandingPageStore : ILandingPageStore { private readonly string _root; public DiskLandingPageStore(IWebHostEnvironment env) { _root = Path.Combine(env.ContentRootPath, "landingpages"); Directory.CreateDirectory(_root); } public async Task SaveAsync(string slug, byte[] bytes) { var safeSlug = Path.GetFileName(slug); // strip any path traversal var path = Path.Combine(_root, $"{safeSlug}.bptl"); await File.WriteAllBytesAsync(path, bytes); } public async Task<byte[]?> LoadAsync(string slug) { var safeSlug = Path.GetFileName(slug); var path = Path.Combine(_root, $"{safeSlug}.bptl"); return File.Exists(path) ? await File.ReadAllBytesAsync(path) : null; } }
Sanitize slugs before joining paths Always run untrusted slugs through Path.GetFileName (or a stricter regex) before composing a file path — otherwise a slug like "../../etc/passwd" reads or writes outside your intended directory.

Step 5b: Persist to Azure Blob Storage

For multi-tenant SaaS, multi-server deployments, or when content needs to be CDN-fronted, write to a blob container instead. Add the Azure.Storage.Blobs NuGet package and swap the implementation:

using Azure.Storage.Blobs; public sealed class AzureLandingPageStore : ILandingPageStore { private readonly BlobContainerClient _container; public AzureLandingPageStore(IConfiguration config) { var connStr = config["Azure:Storage:ConnectionString"] ?? throw new InvalidOperationException("Missing Azure:Storage:ConnectionString"); _container = new BlobContainerClient(connStr, "landingpages"); _container.CreateIfNotExists(); } public async Task SaveAsync(string slug, byte[] bytes) { var blob = _container.GetBlobClient($"{slug}.bptl"); using var ms = new MemoryStream(bytes); await blob.UploadAsync(ms, overwrite: true); } public async Task<byte[]?> LoadAsync(string slug) { var blob = _container.GetBlobClient($"{slug}.bptl"); if (!await blob.ExistsAsync()) return null; var response = await blob.DownloadContentAsync(); return response.Value.Content.ToArray(); } }

Register your chosen implementation in Program.cs:

// Local dev builder.Services.AddSingleton<ILandingPageStore, DiskLandingPageStore>(); // Production builder.Services.AddSingleton<ILandingPageStore, AzureLandingPageStore>();

Step 6: Serve the page publicly

The .bptl file is the editable project format. To render a public-facing page from it, load the same project bytes into a read-only BptLandingPage instance:

@page "/lp/{Slug}" @using Bpt.Components.Tools <BptLandingPage Value="_project" Mode="LandingPageMode.View" ReadOnly="true" Minified="true" /> @code { [Parameter] public string Slug { get; set; } = ""; private LandingPageProject? _project; protected override async Task OnInitializedAsync() { var bytes = await Store.LoadAsync(Slug); if (bytes is { Length: > 0 }) _project = BptlSerializer.Deserialize(bytes); } }
Editor versus viewer Keep editor routes (/admin/landing/...) behind [Authorize(Roles="Editor")] and viewer routes (/lp/...) anonymous. Same component, different Mode, different authorization — that's the whole separation.

An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.