MINISTRYPLATFORM CUSTOM WIDGET

Parish Finder — Installation Guide

A public parish directory that visitors can browse alphabetically or by city, search by name, and filter to the parishes within a chosen distance of their ZIP code — with every parish plotted on a map. No login required, no mapping API key, no billing account.

This guide assumes you have read Getting Started. It covers the Parish Finder and the Parish Detail page it links to.

The Parish Finder: browse A–Z or by city, search by name, or find the parishes nearest a ZIP code, with every parish plotted on the map.

What the page does

The widget reads your Congregations where Available Online is Yes, follows each one to its Location record and that Location's Address, and uses the address coordinates to place a pin on the map and to measure distance.

  • Browse A–Z — grouped under sticky letter headings with a jump bar across the top.
  • Browse by city — grouped by city, with a count beside each city name.
  • Search — narrows the list as the visitor types, matching parish name, location name, street, city, state, ZIP and county. Accents are folded, so avila finds Ávila.
  • Find parishes near me — a ZIP code, city or full address, or one tap on Use my location. Results re-sort nearest-first, numbered 1, 2, 3 to match the map pins, and the chosen radius is drawn as a ring on the map.
  • Map — clicking a pin opens the parish name, address, phone and a Get directions link. Hovering a card highlights its pin; Show on map works the other way round. On phones the list and map become tabs.
  • Diocesan offices — congregations whose name contains Diocese, Archdiocese or Eparchy are shown in their own section pinned above the parish list, so the chancery never appears as the nearest parish.

The Nearest button and its panel are tied together deliberately. The panel sits directly beneath the button with a caret pointing up at it, its heading says what it is for, and pressing Nearest before entering a location highlights the panel and puts the cursor in the box rather than doing nothing. Once a location is set, the heading changes to name it.

The page is entirely public. Nothing here requires a login, and no personal data is read or written.

What you need before starting

SQL Server Management Studio access to the MinistryPlatform database, edit access to the diocesan website, and the files that ship with this guide:

  • deploy_ParishFinder.sql — creates and registers the stored procedure.
  • deploy_ParishFinder_logos.sql — run second; adds parish images from each Congregation's DEFAULT file attachment.
  • parish-finder-embed.html — the entire front end: markup, styling, logic and the widget tag.
  • parish-finder-demo-large.html — optional. A standalone preview with about 240 generated parishes, for judging the browse experience at diocesan scale. Needs no database.
  • fix_ParishFinder_registration.sql — only needed if the API refuses the procedure after the main deploy script reports success.

Skip the SQL for a moment if you like. Open parish-finder-embed.html , change demoMode: false to demoMode: true , and paste it into a draft page. The finder renders with built-in sample parishes so you can settle the colours and headings before the database work is done. Set it back to false when you are ready to point at real data.

If you do not have a dedicated hosting plan or the required SQL Server skills, reach out to the professional services team for assistance. Normal hourly rates will apply.


Step 1 Run the database script

Open deploy_ParishFinder.sql in SSMS, confirm the database dropdown shows the MinistryPlatform database and not master , and execute it. The script does three things, all required:

  1. Creates dbo.api_custom_ParishFinder , which returns the Available Online congregations with their location, address and coordinates.
  2. Registers the procedure in dp_API_Procedures , without which the MinistryPlatform API will not call it.
  3. Links the procedure to the Administrators security role in dp_Role_API_Procedures .

Step 3 is the one most often missed, and the widget will not work without it. The API checks role links, not SQL permissions.

The script ends with verification queries. The first three must each return a row: Proc exists , API registered , Role linked. The fourth prints the parish rows the widget will receive, which is the quickest way to spot a parish with a missing address. It is safe to re-run the script; it will not create duplicates.

If your widget API client uses a role other than Administrators

Change @RoleName — it appears in two places in the script, in the registration block and again in the verification block. Change both.

Why the script looks old-fashioned

Plenty of MinistryPlatform databases still run at a SQL Server 2008 compatibility level even on a modern server, which quietly removes functions you would otherwise reach for. The script therefore avoids TRY_CONVERT , STRING_SPLIT , CREATE OR ALTER , DROP … IF EXISTS , IIF and CONCAT . It also inspects dp_API_Procedures and dp_Role_API_Procedures before inserting, because the key column is named API_Procedure_ID on some versions and Procedure_ID on others, and some versions require a Domain_ID that others do not have. The script prints which column names and domain it found, which is a useful line to keep if you ever need to open a support ticket.

The practical consequence for you: coordinates arrive at the browser as the strings MinistryPlatform stores, and the widget parses them. A parish with a blank or malformed latitude is simply left off the map rather than breaking the query.


Step 2 Verify the API can reach the procedure

In a browser, open the following, replacing yourdiocese with your MinistryPlatform host prefix:

 https://yourdiocese.cloudapps.ministryplatform.cloud/sky/api/CustomWidget?storedProcedure=api_custom_ParishFinder 

You should see JSON listing your congregations. If the response complains that a parameter was not supplied, append &@DomainID=1 and try again.

If instead you see "Procedure … does not exist or user does not have access to it" , go to Troubleshooting below — this is almost always a missed registration step rather than a SQL permission problem.


Step 3 Add the page and configure it

Create a new page on the website — /parishes or /find-a-parish — and add a single HTML embed containing the full contents of parish-finder-embed.html . Everything an administrator normally touches lives in one place: the PF_CONFIG block at the very top of the file. You should not need to read past it.

Setting What it does
dataHost Required. Your MP host prefix — the part of your Platform URL before .ministryplatform.net . Launch the Add/Edit Family tool and take the first element of the hostname if you are unsure.
congregationIDs "" for every Available Online congregation, the usual choice for a diocese; "5" for one; or "5,8,12" for a cluster. Because each page can carry a different value, one deployment can serve a diocesan finder and a separate page per deanery from the same stored procedure.
pageTitle , pageIntro , officesTitle The headings. Set pageIntro to "" to drop the introductory line.
brandColor , brandColorDark , accentColor Buttons, map pins, focus rings and the distance badge. brandColorDark is used for hover states — a noticeably darker shade of the same hue works best.
mapStyle "light" is a clean greyscale basemap that lets the pins carry the colour. "street" is classic OpenStreetMap; "terrain" shows relief.
mapCenter , mapZoom Only used for the instant before the parishes are plotted; the map then fits itself to your pins. Setting them near your diocese avoids a visible jump.
ignoreLeadingTitles true files St. Mary under M and Our Lady of Lavang under L. Leave it on unless your diocese publishes a strict A–Z: in a typical diocese a literal sort puts most of the parishes under S and the letter index stops being useful.
radiusOptions , defaultRadius , units The distance dropdown. Use units: "km" outside the United States and set geocodeCountry to the matching two-letter country code.
showLogos , logoFit , fileBaseUrl The parish image — see Parish images below. logoFit: "cover" fills the square and crops; "contain" letterboxes it. Leave fileBaseUrl empty unless your Platform files are served from an unusual hostname.
showPhone , showDescription , showGivingLink Optional card content, from Location Phone, Congregation Description and Congregation Giving URL respectively. Phone is on by default.
parishDetailPage Everything up to the parish identifier. Defaults to "/parish-detail?id=" , which yields /parish-detail?id=13 . The Congregation ID is appended automatically, the parish name becomes a link, and a Details button appears on each card. A site-relative path is usually best; an absolute URL also works. Set to "" to hide the link and the button.
pageSize , compactThreshold , clusterThreshold Large-diocese behaviour — see the next section. A small diocese never reaches these thresholds, so they can be left alone.
demoMode , debug Testing aids. demoMode: true renders 9 sample parishes without touching MinistryPlatform; demoMode: "large" generates about 240 parishes across a diocese-sized area; debug: true logs the widget's activity to the browser console. All should be false in production.

The parishDetailPage default assumes you have built that page. If /parish-detail does not exist yet, every card will link to a 404 — either create the page or blank this setting before going live.

Parish images

Each card can show the parish's picture, taken from the DEFAULT file attached to the Congregation record — the image that appears top-left on the Organization page in the Platform, marked DEFAULT in the Files panel. This is the right source: it is where staff already put the parish photo, and it needs no extra field to maintain.

It requires one extra script, deploy_ParishFinder_logos.sql , run after the main deploy script. That script adds the attachment's GUID to the procedure's output as Logo_File_GUID , and the widget turns it into an image URL:

 https://{dataHost}.ministryplatform.net/ministryplatformapi/files/{GUID} 

Note the hostname. Files come from {host}.ministryplatform.net , not the {host}.cloudapps.ministryplatform.cloud address the Custom Widget API uses. The cloudapps host does not serve files and returns an error for these URLs.

The fallback order per card is: default file attachment, then the Congregation Logo_URL field, then nothing but the letter badge. That ordering matters because Logo_URL is frequently stale — on the demo instance every congregation pointed at the same S3 diocesan logo, and that URL no longer resolves at all.

Check this in a private window. Paste one of the GUIDs from the script's verification output into the URL above and open it in a private browsing window. In a normal window you are probably signed in to the Platform, which would hide the fact that a file is not reachable by an anonymous visitor — and every visitor to your parish finder is anonymous.

If images still do not appear

Symptom Cause and fix
Verification query 3a says NO DEFAULT FILE The congregation has no attachment, or has one that is not flagged DEFAULT. Open the Organization record, Files panel, and click the star on the image you want. Where no file is flagged, the script falls back to the earliest attachment.
The card shows only the letter badge Both sources were empty or both failed. Open the browser console: a failed image logs a network error against the URL it tried, which tells you whether the GUID or the hostname is the problem.
Images are cropped oddly Set logoFit: "contain" . The default is "cover" because most parishes attach a photograph of the church, which looks better filling the square; a true logo with transparent margins wants "contain" .
Images appear in Cards but not in List That is deliberate. The compact list density drops the image and keeps the letter badge, which is what ties each row to its map pin.

Do not set showLogos: true if every congregation shares one diocesan logo. Identical thumbnails on every card add visual weight without adding information. Either give each parish its own image in the Platform or turn the option off.


The Parish Detail page

The page the Finder links to. It shows one parish: a full-width photograph, the address, phone, pastor, parish office contact, the weekly Mass schedule, a map and a directions link.

The Parish Detail page behind each result: photograph, address, pastor, parish office, weekly Mass schedule and directions.
  • deploy_ParishDetail.sql — the stored procedure. Run it after both Parish Finder scripts; it reuses the logo view they create.
  • parish-detail-embed.html — the page itself.

How the parish is chosen

By the query string. The Finder links to /parish-detail?id=13 , and the widget tag passes that straight through to the procedure:

 data-params="@CongregationID=[id]" 

The square brackets are the Custom Widget toolkit's query-string binding — it reads ?id= from the current URL. So one embed serves every parish in the diocese and there is nothing to configure per parish. If you change the Finder's parishDetailPage to use a different parameter name, change idParam in the detail page's config to match.

Two result sets

The procedure returns DataSet1 — one row, the parish — and DataSet2, its Mass schedule. Individual Mass events are collapsed to one row per weekday and time, so a daily 7am Mass appears once against each weekday rather than 28 times across the four-week window. Occurrence counts come back too, which is how a Mass that happens once a month is marked occasional instead of being presented as a weekly fixture.

Check your Mass event type. @MassEventTypeID defaults to 13 , which is Mass on this instance. Verify yours on the Event Types page in the Platform. Set it to 0 to omit the schedule entirely. The procedure counts only events that are not cancelled and are approved for the web; a commented line lets you also require a particular visibility level.

The pastor's name

Congregations stores the pastor as a dp_Users reference, and those display names are frequently login names — Yushachkov , FatherBenn , PastorBob on this instance. The procedure follows that user through to their Contact record and returns the contact's display name instead, which is what a visitor should read. If a pastor's contact record is thin, the name will still look odd; fix it on the Contact, not in the SQL.

The hero image

The parish photograph fills a band heroHeight pixels tall — 420 by default, 240 on a phone — with the parish name and address over it in white on a black wash. heroOverlay controls the strength of that wash, 0.45 by default; raise it if a parish attaches a very bright photograph and the name still competes.

If the parish name looks translucent or grey rather than white, your theme is setting -webkit-text-fill-color on headings, which silently overrides color and cannot be fixed by changing the colour alone. The embed already forces color , -webkit-text-fill-color , opacity and background on the hero text for exactly this reason. If a theme still breaks through, it is using its own ID selector — add #pd-root to the front of the offending rule.

Sparse records degrade rather than break

What is missing What the visitor sees
No default file attachment The hero becomes a brand-coloured panel carrying the parish monogram, taken from the significant word in the name — Our Lady of Lavang gives L. Never a grey hole.
No coordinates Map and directions button are omitted; everything else renders.
No Mass events The section stays, saying no times are published and to contact the office — which is more useful than silently hiding it, since a missing schedule is usually a data problem worth surfacing.
Address, map and actions all missing The layout collapses to a single column rather than leaving half the page blank.
No id in the URL, or an unknown id A "we could not find that parish" panel with a link back to the directory. A parish that is not Available Online cannot be reached by guessing its id — the procedure enforces the same gate as the Finder.

Browsing a large diocese

Nine parishes and 250 parishes are different design problems. Everything below switches itself on as the list grows, so a small diocese sees none of it and has nothing to configure.

Behaviour How it works
Paged rendering Cards arrive pageSize at a time — 60 by default — with more loading as the visitor scrolls, plus an explicit Show N more button for anyone who does not scroll. The count line always states the honest total: 243 parishes — showing the first 60.
Compact density Past compactThreshold matches (80 by default) cards collapse to a one-line list, because 250 tall cards is a scroll marathon. Visitors can switch between Cards and List at any time with the density toggle, which overrides the automatic choice.
Map clustering Past clusterThreshold pins (40 by default) the map groups them into counted clusters that split apart as you zoom. Without this, a diocesan map is an unreadable pile of overlapping pins. Selecting a parish in the list zooms the map in far enough to break its cluster open before showing the popup.
Letter index The A–Z bar sticks to the top of the list while scrolling, greys out letters with no parishes, and shows the count for each letter on hover. Jumping to a letter that has not been rendered yet loads everything up to it first, so the jump always lands.
Where the letter appears In the section heading, as a coloured chip beside its count — C · 24 parishes — and not on the individual cards. Repeating it per card duplicated what the heading already said, and placing it on the corner of the parish photograph obscured the picture. Cards carry a badge only in Nearest mode, where the number is the parish's distance ranking and does carry information. Map pins still show the letter, which is what ties a pin back to its heading.
Grouping never splits Paging emits whole letter or city groups, so a heading always has cards beneath it — you never see a lone "S" at the bottom of a batch.

This is where ignoreLeadingTitles earns its keep. In the 240-parish preview, ignoring leading titles spreads the parishes across 24 letters, the largest being 24 under C. Set it to false and the same list collapses into a handful of enormous S, O and H groups, which makes the letter index close to useless. Check the distribution on your own data before changing it.

Open parish-finder-demo-large.html in a browser to try all of this. It needs no database and no configuration — it generates its parishes from a fixed seed, so the list is identical every time.


Step 4 Test

  1. Open the page. You should see the toolbar, the parish list grouped under letter headings, and a map with one pin per parish.
  2. Type part of a parish name. The list and the map should both narrow, and a removable chip should appear showing the active search.
  3. Switch to By City. Sections should become city names with counts.
  4. Enter a ZIP code, choose a radius, and press Search. The list should re-sort nearest-first with numbered badges, the count line should read N parishes within X miles of … , and a ring should be drawn on the map. Widening the radius should bring more parishes in.
  5. Click a pin. The popup should show the address, the phone number if you publish one, the distance, and a working Get directions link.
  6. Narrow the browser to phone width. List and Map should become tabs.
  7. Press Reset filters. Everything should return to the full A–Z list.

MinistryPlatform configuration

The widget is only as good as the Congregation records behind it. For each parish that should appear:

Field Requirement
Congregations → Available Online Yes. This is the only switch that controls whether a parish appears. Anything set to No is invisible to the widget.
Congregations → Congregation Name What visitors see, and what the search matches. Consistent naming pays off: pick St. or Saint and stay with it.
Congregations → Location Must point at the parish's Location record. A congregation with no Location still appears in the list, labelled Address not published , but cannot be mapped or measured.
Locations → Address Street, city, state and postal code. The full address is also what the Directions button hands to Google Maps.
Addresses → Latitude / Longitude Required for the map and the distance search. If these are empty the parish is listed but not pinned. Clear Do Not Validate and let MinistryPlatform geocode the address, or enter the coordinates by hand.
Locations → Phone Optional. Shown as a tap-to-call link when showPhone is on.
Congregations → Online Sort Order Optional. Used only as a tie-breaker; the widget's own A–Z, city and distance ordering takes precedence on the page.
Congregations → End Date, Coming Soon The procedure hides congregations with a past End Date and those flagged Coming Soon. This is how you retire a closed or merged parish from the page — set the End Date rather than deleting the record.

The quickest audit is the last verification query in the deploy script. Any row with a blank Latitude or Longitude is a parish that will list but not map.


Troubleshooting

Blocks of documentation text appear on the page, or labels like "Filter by city" are visible.

Both are website-builder side effects, and both are already handled in the current embed — if you are seeing them, you have an earlier copy of the file.

Several builders, Duda among them, strip the <!-- and --> markers from an HTML embed and render what was inside as ordinary page text. The embed therefore contains no HTML comments at all; every note lives in a JavaScript or CSS comment instead. If you add commentary of your own, do the same.

The finder uses visually-hidden <label> elements so that screen-reader users know what each field is. Host stylesheets frequently reset those to normal positioning, which dumps Search parishes by name or city and Filter by city onto the page. The rule is now forced. Do not delete the labels to work around it — that removes the accessibility they provide.

Everything is centred, the fonts are wrong, or the icon overlaps the text in a search box.

The host theme is winning the specificity contest. Every rule in the embed is now scoped under the #pf-root ID, which beats any selector that does not itself contain an ID, and alignment, font size and form-control padding are re-asserted rather than inherited. If your theme still breaks through, it is using !important or its own ID selector; add #pf-root to the front of the offending rule in the embed's stylesheet.

The page says "dataHost has not been set".

Exactly what it says: dataHost at the top of the configuration block is still empty. Note that the widget tag is configured by a small inline script that sits directly beneath it, deliberately, because customWidgetV1.js initialises on DOMContentLoaded — configuring the tag any later means the widget fires first, with no host, and returns nothing. Do not move or remove that script.

"'TRY_CONVERT' is not a recognized built-in function name" or "'STRING_SPLIT' … invalid object name".

You are running an older version of the deploy script against a database at a low compatibility level. The current script does not use either function. If you see these errors, you have an earlier copy — take a fresh one. Do not raise the database compatibility level to work around it; that is a decision for whoever owns the MinistryPlatform instance, not a side effect of installing a widget.

"Cannot insert the value NULL into column 'Domain_ID', table '…dp_Role_API_Procedures'".

Your MP version requires a domain on the role link. The current script detects this and supplies it. Note that when this insert fails, the script may still print "Linked … to role" — the print statement runs even though the insert was rolled back, so trust verification query 4c, not the messages. If 4c returns no row, the link is not there.

"Could not find stored procedure 'dbo.api_custom_ParishFinder'" at the end of the script.

The CREATE PROCEDURE in Step 1 failed, so the preview at the end had nothing to run. Scroll up in the messages pane to the first error — that is the real one. Everything after it is fallout.

Verification query 4b shows the name in square brackets — is that wrong?

No. That column is QUOTENAME(Procedure_Name) , and QUOTENAME adds the brackets so that a leading or trailing space would be visible to you rather than invisible. The brackets are not stored in the table. The column to read is Name_Clean, which says OK when the stored value matches exactly, alongside a Name_Length of 23.

"Procedure … does not exist or user 'apiuser' does not have access to it." — but all three checks passed.

This is the awkward case: the procedure exists, the registration row is there, the role link is there, and the API still refuses. Two things are worth knowing.

First, prove the endpoint itself is healthy by requesting a custom widget procedure that already works on the instance. If that returns HTTP 200 and yours returns HTTP 500, then the endpoint, the anonymous client and the role mechanism are all fine, and the difference is confined to your procedure's registration row or its object permissions.

Second, run fix_ParishFinder_registration.sql . Rather than guessing which column matters, it rebuilds the Parish Finder registration as a column-for-column clone of a procedure you know works, copies that procedure's role links, and mirrors its EXECUTE grants. It prints both rows side by side before and after, so you can see what was actually different. Set @Model at the top to the working procedure's name.

If the rows then match and it still fails, the cause is the API's cached procedure list: recycle the MinistryPlatform API application pool in IIS, or wait for the scheduled recycle. Full steps are in Getting Started.

"Procedure … does not exist or user 'apiuser' does not have access to it."

This message comes from the MinistryPlatform API, not SQL Server, and almost always means a missed registration step. Check in order:

  • The procedure exists in the right database and in the dbo schema: SELECT SCHEMA_NAME(schema_id), name FROM sys.procedures WHERE name LIKE 'api_custom%'
  • A row exists in dp_API_Procedures with the exact name and no trailing spaces: SELECT QUOTENAME(Procedure_Name), LEN(Procedure_Name) FROM dp_API_Procedures — expect a length of 23.
  • A row links it to a role in dp_Role_API_Procedures .

The deploy script's verification queries check all three.

The page shows "The parish list could not be loaded."

The widget did not return data within twelve seconds. Nearly always dataHost is wrong. Confirm it, then open the Step 2 URL directly — the error message on the page includes the exact URL to try. Setting debug: true logs the widget's request and response to the browser console (F12).

The page loads but says no parishes were returned.

The widget reached MinistryPlatform and got an empty set. Either no congregation has Available Online set to Yes, or congregationIDs lists IDs that do not match. Run the last verification query in the deploy script to see what the database actually returns.

A parish is in the list but has no pin, and never appears in a distance search.

Its address has no coordinates. Open the Address record in the Platform, clear Do Not Validate , and let MinistryPlatform geocode it, or type the latitude and longitude in directly. Diocesan offices are deliberately exempt from the radius filter, so the office stays visible regardless of the search.

A pin is in the wrong place.

The coordinates on the Address record are wrong — commonly a geocode that landed on a street centroid, or a latitude and longitude entered the wrong way round. Compare against the parish's real position and correct the Address record; the widget will follow.

"We could not find that place" when a visitor enters a ZIP code.

Address lookup uses the OpenStreetMap Nominatim service, restricted to the country in geocodeCountry . Confirm that code is right for your diocese. Nominatim also rate-limits heavy automated use; ordinary visitor traffic is well within its terms, but if you are load-testing you will be throttled.

The map is missing entirely, though the list works.

Leaflet did not load. Check that your site's content security policy allows scripts and styles from cdnjs.cloudflare.com and map tiles from basemaps.cartocdn.com , or tile.openstreetmap.org if you chose mapStyle: "street" . The finder is written to degrade gracefully: the searchable, sortable list keeps working without a map.

Changes to the API registration don't take effect.

The API may cache its procedure list. Recycle the MinistryPlatform API application pool in IIS, or wait for the scheduled recycle. The widget caches responses for five minutes as well, so a hard refresh helps.


Customization notes

Colours come from the three settings in PF_CONFIG ; they are injected as CSS custom properties at runtime, so there is no second place to keep in sync. If you want to go further, every rule in the stylesheet is namespaced under #pf-root and the custom properties are declared in one block at the top of it — override --pf-radius for squarer cards, or --pf-line and --pf-bg-soft to match a darker site theme.

Two behaviours are worth knowing about because they look like bugs until you know they are deliberate: scroll-wheel zoom on the map stays off until the visitor clicks the map, so that scrolling down the page is never hijacked; and the distance filter never hides a diocesan office, because people look for the chancery by name rather than by proximity.

The stored procedure is the place to change what data reaches the page. Its filters are plainly written and commented — if your diocese wants Coming Soon parishes listed, or wants to include congregations regardless of End Date, those are one-line edits. Adding a field means adding it to the SELECT , to the Liquid template's data attributes, and to the ingest function in the embed, in that order.


Attribution

Map rendering uses Leaflet with tiles from CARTO and OpenStreetMap; address lookup uses OpenStreetMap Nominatim. The required attribution is displayed in the corner of the map and must not be removed. None of these services require an account, an API key or a billing relationship, which is the reason they were chosen over Google Maps for a widget that will be handed to many dioceses.

Built on the MinistryPlatform Custom Widget framework ( MPCustomWidgets).

ACST powered by Vanco · 800-736-7425 · allsales@acst.com